From ffe2e9cc82771bc0c523d4ead472c43528b2660f Mon Sep 17 00:00:00 2001 From: Curtish Date: Tue, 7 Jan 2025 16:38:53 +0000 Subject: [PATCH] wip2 Signed-off-by: Curtish --- .eslintrc | 1 - .../e2e-tests/src/abilities/WalletSdk.ts | 5 +- .../src/workflow/CloudAgentWorkflow.ts | 4 +- integration-tests/node/assertions.cjs | 34 +- src/domain/buildingBlocks/Pollux.ts | 24 - src/domain/index.ts | 1 - src/domain/models/VerifiableCredential.ts | 4 +- src/domain/protocols/Payload.ts | 2 +- src/edge-agent/Agent.Backup.ts | 42 +- src/edge-agent/Agent.ts | 22 +- .../connectionsManager/ConnectionsManager.ts | 58 +- src/edge-agent/didcomm/Agent.ts | 94 +- src/edge-agent/didcomm/Context.ts | 2 + src/edge-agent/didcomm/CreatePresentation.ts | 12 +- .../didcomm/CreatePresentationRequest.ts | 7 +- .../didcomm/HandleIssueCredential.ts | 13 +- .../didcomm/HandleOfferCredential.ts | 12 +- src/edge-agent/didcomm/HandlePresentation.ts | 22 +- .../helpers/RevealCredentialFields.ts | 11 +- src/edge-agent/helpers/RunProtocol.ts | 65 + src/edge-agent/oidc/Agent.ts | 28 +- .../oidc/tasks/HandleCredentialRequest.ts | 3 +- src/edge-agent/types/index.ts | 1 + src/index.ts | 14 +- src/mercury/didcomm/Wrapper.ts | 4 +- src/plugins/Plugin.ts | 36 + src/plugins/PluginManager.ts | 30 + src/plugins/index.ts | 45 +- .../internal}/anoncreds/CredentialIssue.ts | 9 +- .../internal}/anoncreds/CredentialOffer.ts | 7 +- .../anoncreds/FetchCredentialDefinition.ts | 3 +- .../internal}/anoncreds/FetchSchema.ts | 0 .../internal}/anoncreds/GetLinkSecret.ts | 3 +- .../anoncreds/PresentationRequest.ts | 29 +- .../internal}/anoncreds/PresentationVerify.ts | 11 +- src/plugins/internal/anoncreds/index.ts | 18 + .../anoncreds/module}/AnoncredsLoader.ts | 5 +- .../internal}/anoncreds/types.ts | 0 .../internal}/dif/IsCredentialRevoked.ts | 24 +- .../internal}/dif/PresentationRequest.ts | 32 +- .../internal}/dif/PresentationVerify.ts | 41 +- src/plugins/internal/dif/index.ts | 20 + .../module}/CreatePresentationDefinition.ts | 54 +- src/plugins/internal/dif/module/index.ts | 20 + .../plugins => plugins/internal}/dif/types.ts | 1 - .../internal/oea/index.ts} | 4 - .../internal}/oea/jwt/CredentialIssue.ts | 6 +- .../internal}/oea/jwt/CredentialOffer.ts | 7 +- .../internal}/oea/jwt/PresentationRequest.ts | 24 +- .../internal/oea/jwt}/index.ts | 1 - .../internal}/oea/sdjwt/CredentialIssue.ts | 6 +- .../internal}/oea/sdjwt/CredentialOffer.ts | 9 +- .../oea/sdjwt/PresentationRequest.ts | 14 +- .../internal/oea/sdjwt}/index.ts | 1 - .../plugins => plugins/internal}/oea/types.ts | 0 src/plugins/types.ts | 14 + src/pollux/PlugPol.ts | 57 - src/pollux/index.ts | 2 - .../models/SDJWTVerifiableCredential.ts | 3 +- src/pollux/plugins/anoncreds/Plugin.ts | 24 - src/pollux/plugins/dif/Plugin.ts | 35 - src/pollux/plugins/dif/index.ts | 16 - src/pollux/plugins/oea/index.ts | 16 - .../plugins/oea/jwt/PresentationVerify.ts | 17 - .../plugins/oea/sdjwt/PresentationVerify.ts | 20 - src/pollux/utils/jwt/JWT.ts | 14 +- src/pollux/utils/jwt/PKInstance.ts | 10 +- src/pollux/utils/jwt/index.ts | 17 +- src/utils/tasks.ts | 25 +- tests/agent/Agent.ConnectionsManager.test.ts | 44 +- tests/agent/Agent.anoncreds.test.ts | 16 +- tests/agent/Agent.functional.test.ts | 4 - tests/agent/Agent.test.ts | 193 +- tests/agent/didcomm/Agent.functional.test.ts | 6 - tests/agent/didcomm/invitation.test.ts | 1 - tests/agent/oidc/Agent.functional.test.ts | 6 - .../oidc/task.ParseCredentialOffer.test.ts | 4 +- tests/fixtures/credentials/jwt.ts | 25 +- .../plugins/anoncreds/AnoncredsLoader.test.ts | 175 ++ .../anoncreds/PresentationRequest.test.ts | 276 +++ .../anoncreds/PresentationVerify.test.ts | 1769 +++++++++++++++++ .../dif/CreatePresentationDefinition.test.ts | 46 + tests/plugins/dif/IsCredentialRevoked.test.ts | 530 +++++ tests/plugins/dif/PresentationRequest.test.ts | 156 ++ tests/plugins/dif/PresentationVerify.test.ts | 271 +++ tests/plugins/oea/jwt/CredentialIssue.test.ts | 42 + .../plugins/oea/sdjwt/CredentialIssue.test.ts | 41 + .../oea/sdjwt/PresentationRequest.test.ts | 69 + tests/pollux/Pollux.revocation.test.ts | 170 +- vitest.config.mjs | 2 +- 90 files changed, 4151 insertions(+), 910 deletions(-) delete mode 100644 src/domain/buildingBlocks/Pollux.ts create mode 100644 src/edge-agent/helpers/RunProtocol.ts create mode 100644 src/plugins/Plugin.ts create mode 100644 src/plugins/PluginManager.ts rename src/{pollux/plugins => plugins/internal}/anoncreds/CredentialIssue.ts (86%) rename src/{pollux/plugins => plugins/internal}/anoncreds/CredentialOffer.ts (86%) rename src/{pollux/plugins => plugins/internal}/anoncreds/FetchCredentialDefinition.ts (84%) rename src/{pollux/plugins => plugins/internal}/anoncreds/FetchSchema.ts (100%) rename src/{pollux/plugins => plugins/internal}/anoncreds/GetLinkSecret.ts (90%) rename src/{pollux/plugins => plugins/internal}/anoncreds/PresentationRequest.ts (54%) rename src/{pollux/plugins => plugins/internal}/anoncreds/PresentationVerify.ts (91%) create mode 100644 src/plugins/internal/anoncreds/index.ts rename src/{pollux/plugins/anoncreds => plugins/internal/anoncreds/module}/AnoncredsLoader.ts (98%) rename src/{pollux/plugins => plugins/internal}/anoncreds/types.ts (100%) rename src/{pollux/plugins => plugins/internal}/dif/IsCredentialRevoked.ts (91%) rename src/{pollux/plugins => plugins/internal}/dif/PresentationRequest.ts (78%) rename src/{pollux/plugins => plugins/internal}/dif/PresentationVerify.ts (91%) create mode 100644 src/plugins/internal/dif/index.ts rename src/{pollux/plugins/dif => plugins/internal/dif/module}/CreatePresentationDefinition.ts (55%) create mode 100644 src/plugins/internal/dif/module/index.ts rename src/{pollux/plugins => plugins/internal}/dif/types.ts (99%) rename src/{pollux/plugins/oea/Plugin.ts => plugins/internal/oea/index.ts} (74%) rename src/{pollux/plugins => plugins/internal}/oea/jwt/CredentialIssue.ts (76%) rename src/{pollux/plugins => plugins/internal}/oea/jwt/CredentialOffer.ts (90%) rename src/{pollux/plugins => plugins/internal}/oea/jwt/PresentationRequest.ts (68%) rename src/{pollux/plugins/oea/sdjwt => plugins/internal/oea/jwt}/index.ts (74%) rename src/{pollux/plugins => plugins/internal}/oea/sdjwt/CredentialIssue.ts (76%) rename src/{pollux/plugins => plugins/internal}/oea/sdjwt/CredentialOffer.ts (86%) rename src/{pollux/plugins => plugins/internal}/oea/sdjwt/PresentationRequest.ts (75%) rename src/{pollux/plugins/oea/jwt => plugins/internal/oea/sdjwt}/index.ts (74%) rename src/{pollux/plugins => plugins/internal}/oea/types.ts (100%) create mode 100644 src/plugins/types.ts delete mode 100644 src/pollux/PlugPol.ts delete mode 100644 src/pollux/index.ts delete mode 100644 src/pollux/plugins/anoncreds/Plugin.ts delete mode 100644 src/pollux/plugins/dif/Plugin.ts delete mode 100644 src/pollux/plugins/dif/index.ts delete mode 100644 src/pollux/plugins/oea/index.ts delete mode 100644 src/pollux/plugins/oea/jwt/PresentationVerify.ts delete mode 100644 src/pollux/plugins/oea/sdjwt/PresentationVerify.ts create mode 100644 tests/plugins/anoncreds/AnoncredsLoader.test.ts create mode 100644 tests/plugins/anoncreds/PresentationRequest.test.ts create mode 100644 tests/plugins/anoncreds/PresentationVerify.test.ts create mode 100644 tests/plugins/dif/CreatePresentationDefinition.test.ts create mode 100644 tests/plugins/dif/IsCredentialRevoked.test.ts create mode 100644 tests/plugins/dif/PresentationRequest.test.ts create mode 100644 tests/plugins/dif/PresentationVerify.test.ts create mode 100644 tests/plugins/oea/jwt/CredentialIssue.test.ts create mode 100644 tests/plugins/oea/sdjwt/CredentialIssue.test.ts create mode 100644 tests/plugins/oea/sdjwt/PresentationRequest.test.ts diff --git a/.eslintrc b/.eslintrc index c625e6264..32f544109 100644 --- a/.eslintrc +++ b/.eslintrc @@ -28,7 +28,6 @@ "comma-dangle": 0, "no-unexpected-multiline": "warn", "prefer-const": "warn", - "@typescript-eslint/ban-types": "warn", "@typescript-eslint/no-empty-function": "off", "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off", diff --git a/integration-tests/e2e-tests/src/abilities/WalletSdk.ts b/integration-tests/e2e-tests/src/abilities/WalletSdk.ts index 03ae50e6a..7aee684a0 100644 --- a/integration-tests/e2e-tests/src/abilities/WalletSdk.ts +++ b/integration-tests/e2e-tests/src/abilities/WalletSdk.ts @@ -1,5 +1,5 @@ import { Ability, Discardable, Initialisable, Interaction, Question, QuestionAdapter } from "@serenity-js/core" -import type SDK from "@hyperledger/identus-edge-agent-sdk" +import SDK from "@hyperledger/identus-edge-agent-sdk" import axios from "axios" import { axiosInstance, CloudAgentConfiguration } from "../configuration/CloudAgentConfiguration" import InMemoryStore from "../configuration/inmemory" @@ -173,7 +173,8 @@ export class WalletSdk extends Ability implements Initialisable, Discardable { pluto, mediatorDID, castor - }) + }); + this.sdk.plugins.register(SDK.Plugins.Anoncreds); this.sdk.addListener( ListenerKey.MESSAGE, async (messages: SDK.Domain.Message[]) => { diff --git a/integration-tests/e2e-tests/src/workflow/CloudAgentWorkflow.ts b/integration-tests/e2e-tests/src/workflow/CloudAgentWorkflow.ts index 00e4bac9a..7b28cb9b4 100644 --- a/integration-tests/e2e-tests/src/workflow/CloudAgentWorkflow.ts +++ b/integration-tests/e2e-tests/src/workflow/CloudAgentWorkflow.ts @@ -14,8 +14,6 @@ import { } from "@amagyar-iohk/identus-cloud-agent-client-ts" import { CloudAgentConfiguration } from "../configuration/CloudAgentConfiguration" import { Utils } from "../Utils" -import SDK from "@hyperledger/identus-edge-agent-sdk" -import { JWTRevocationStatus } from "@hyperledger/identus-edge-agent-sdk/build/domain" export class CloudAgentWorkflow { static async createConnection(cloudAgent: Actor, label?: string, goalCode?: string, goal?: string) { @@ -306,7 +304,7 @@ export class CloudAgentWorkflow { const credentialResponse = await this.getCredential(cloudAgent, recordId) const jwtString = Utils.decodeBase64URL(credentialResponse.credential) const decoded = CloudAgentWorkflow.instance.JWTCredential.fromJWS(jwtString) - const credentialStatus = decoded.vc.credentialStatus as JWTRevocationStatus + const credentialStatus = decoded.vc.credentialStatus as any const statusList = credentialStatus.statusListCredential statusRegistry.set(recordId, statusList) } diff --git a/integration-tests/node/assertions.cjs b/integration-tests/node/assertions.cjs index 8b52a5973..72b6a8fd1 100644 --- a/integration-tests/node/assertions.cjs +++ b/integration-tests/node/assertions.cjs @@ -9,7 +9,6 @@ const runTests = (describe, test, assert, SDK) => { assert("Domain" in SDK); assert("Mercury" in SDK); assert("Pluto" in SDK); - assert("Pollux" in SDK); // misc modules assert("ApiImpl" in SDK); @@ -64,9 +63,6 @@ const runTests = (describe, test, assert, SDK) => { // ?? should be in Pluto assert("Store" in SDK); - - // ?? shouldnt be exported - assert("isPresentationDefinitionRequestType" in SDK); }); describe("Modules", () => { @@ -126,21 +122,21 @@ const runTests = (describe, test, assert, SDK) => { }); }); - describe("Pollux", () => { - test("instantiates", async () => { - const apollo = new SDK.Apollo(); - const castor = new SDK.Castor(apollo); - const pollux = new SDK.Pollux(apollo, castor); - assert(pollux instanceof SDK.Pollux); - assert(pollux.revealCredentialFields instanceof Function); - assert(pollux.isCredentialRevoked instanceof Function); - assert(pollux.parseCredential instanceof Function); - assert(pollux.processCredentialOffer instanceof Function); - assert(pollux.createPresentationSubmission instanceof Function); - assert(pollux.verifyPresentationSubmission instanceof Function); - assert(pollux.createPresentationDefinitionRequest instanceof Function); - }); - }); + // describe("Pollux", () => { + // test("instantiates", async () => { + // const apollo = new SDK.Apollo(); + // const castor = new SDK.Castor(apollo); + // const pollux = new SDK.Pollux(apollo, castor); + // assert(pollux instanceof SDK.Pollux); + // assert(pollux.revealCredentialFields instanceof Function); + // assert(pollux.isCredentialRevoked instanceof Function); + // assert(pollux.parseCredential instanceof Function); + // assert(pollux.processCredentialOffer instanceof Function); + // assert(pollux.createPresentationSubmission instanceof Function); + // assert(pollux.verifyPresentationSubmission instanceof Function); + // assert(pollux.createPresentationDefinitionRequest instanceof Function); + // }); + // }); }); test("Agent starts", async () => { diff --git a/src/domain/buildingBlocks/Pollux.ts b/src/domain/buildingBlocks/Pollux.ts deleted file mode 100644 index d84e44053..000000000 --- a/src/domain/buildingBlocks/Pollux.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { JsonObj } from "../../utils"; -import { Payload } from "../protocols/Payload"; - -/** - * Pollux - * handle Credential related tasks - */ -export interface Pollux { - handle( - protocolType: Pollux.ProtocolType, - protocolId: string | undefined, - data: JsonObj, - ): Promise; -} - -export namespace Pollux { - - export type ProtocolType = - | "credential-issue" - | "credential-offer" - | "presentation-request" - | "presentation-verify" - | "revocation-check"; -} diff --git a/src/domain/index.ts b/src/domain/index.ts index ba706bbf0..907943221 100644 --- a/src/domain/index.ts +++ b/src/domain/index.ts @@ -6,5 +6,4 @@ export * from "./buildingBlocks/Apollo"; export * from "./buildingBlocks/Castor"; export * from "./buildingBlocks/Mercury"; export * from "./buildingBlocks/Pluto"; -export * from "./buildingBlocks/Pollux"; export * from "./utils/JWT"; diff --git a/src/domain/models/VerifiableCredential.ts b/src/domain/models/VerifiableCredential.ts index 932f09e0c..97834aacb 100644 --- a/src/domain/models/VerifiableCredential.ts +++ b/src/domain/models/VerifiableCredential.ts @@ -1,6 +1,6 @@ // TODO remove this when removing fn from Agent -import { OEA } from "../../pollux/plugins/oea/types"; -import { Claims as ACClaims } from "../../pollux/plugins/anoncreds/types"; +import { OEA } from "../../plugins/internal/oea/types"; +import { Claims as ACClaims } from "../../plugins/internal/anoncreds/types"; export type PresentationClaims = T extends CredentialType.JWT ? OEA.JWTPresentationClaims : diff --git a/src/domain/protocols/Payload.ts b/src/domain/protocols/Payload.ts index e4fdd2f85..87cae7865 100644 --- a/src/domain/protocols/Payload.ts +++ b/src/domain/protocols/Payload.ts @@ -11,5 +11,5 @@ export interface Payload { } export namespace Payload { - export const make = (pid: string, data: any) => ({ pid, data }); + export const make = (pid: string, data: any): Payload => ({ pid, data }); } diff --git a/src/edge-agent/Agent.Backup.ts b/src/edge-agent/Agent.Backup.ts index 5be2760e3..8d9869f2d 100644 --- a/src/edge-agent/Agent.Backup.ts +++ b/src/edge-agent/Agent.Backup.ts @@ -3,19 +3,14 @@ import jweWasm from "jwe-wasm/jwe_rust_bg.wasm"; import * as Domain from "../domain"; import Agent from "./Agent"; import { Version } from "../domain/backup"; -// import { isObject, validateSafe } from "../utils"; -// import * as Domain from "../domain"; -// import Agent from "./Agent"; import { isNil, isObject, notNil, validateSafe } from "../utils"; /** * define Agent requirements for Backup */ -// type BackupAgent = Pick; type BackupAgent = Pick; type BackupExclude = "messages" | "mediators" | "link_secret"; - type MasterKey = Domain.PrivateKey & Domain.ExportableKey.Common & Domain.ExportableKey.JWK & Domain.ExportableKey.PEM; export type BackupOptions = { @@ -33,23 +28,22 @@ export class AgentBackup { ) {} /** - * Creates a JWE (JSON Web Encryption) containing the backup data stored in Pluto. - * The data can optionally be encrypted using a custom master key, compressed, - * and filtered to exclude specified fields. - * - * @param {BackupOptions} [options] - Optional settings for the backup. - * @param {Version} [options.version] - Specifies the version of the backup data. - * @param {MasterKey} [options.key] - Custom master key used for encrypting the backup. - * @param {boolean} [options.compress] - If true, compresses the JWE using DEFLATE. - * @param {BackupExclude[]} [options.excludes] - Keys to exclude from the backup data - * (e.g., "messages", "mediators", "link_secret"). Arrays are cleared, and strings are set to empty strings. - * - * @returns {Promise} - A promise that resolves to the JWE string. - * - * @see restore - Method to restore data from a JWE string. - */ + * Creates a JWE (JSON Web Encryption) containing the backup data stored in Pluto. + * The data can optionally be encrypted using a custom master key, compressed, + * and filtered to exclude specified fields. + * + * @param {BackupOptions} [options] - Optional settings for the backup. + * @param {Version} [options.version] - Specifies the version of the backup data. + * @param {MasterKey} [options.key] - Custom master key used for encrypting the backup. + * @param {boolean} [options.compress] - If true, compresses the JWE using DEFLATE. + * @param {BackupExclude[]} [options.excludes] - Keys to exclude from the backup data + * (e.g., "messages", "mediators", "link_secret"). Arrays are cleared, and strings are set to empty strings. + * + * @returns {Promise} - A promise that resolves to the JWE string. + * + * @see restore - Method to restore data from a JWE string. + */ async createJWE(options?: BackupOptions): Promise { - // const backup = await this.Agent.pluto.backup(); let backup = await this.Agent.pluto.backup(options?.version); if (options?.excludes && Array.isArray(options.excludes)) { @@ -85,7 +79,6 @@ export class AgentBackup { */ async restore(jwe: string, options?: BackupOptions) { const masterSk = await this.masterSk(options); - // const masterSk = await this.masterSk(); const jwk = masterSk.to.JWK(); const JWE = await this.getJWE(); const decoded = JWE.decrypt( @@ -153,8 +146,9 @@ export class AgentBackup { * @returns JWK */ private async masterSk(options?: BackupOptions) { - if (notNil(options?.key)) { - return options.key; + const optKey = options?.key; + if (notNil(optKey)) { + return optKey; } const masterKey = this.Agent.apollo.createPrivateKey({ diff --git a/src/edge-agent/Agent.ts b/src/edge-agent/Agent.ts index 3d49b5302..e2182fd81 100644 --- a/src/edge-agent/Agent.ts +++ b/src/edge-agent/Agent.ts @@ -1,7 +1,6 @@ import * as Domain from "../domain"; import Apollo from "../apollo"; import Castor from "../castor"; -import Pollux from "../pollux"; import { Startable } from "../domain/protocols/Startable"; import { AgentBackup } from "./Agent.Backup"; import { SignWithDID } from "./didFunctions/Sign"; @@ -10,6 +9,7 @@ import { FetchApi } from "./helpers/FetchApi"; import { Task } from "../utils/tasks"; import { notNil } from "../utils"; import { RevealCredentialFields } from "./helpers/RevealCredentialFields"; +import { RunProtocol } from "./helpers/RunProtocol"; /** * Edge agent implementation @@ -20,7 +20,6 @@ import { RevealCredentialFields } from "./helpers/RevealCredentialFields"; */ export default class Agent extends Startable.Controller { public backup: AgentBackup; - public readonly pollux: Domain.Pollux; /** * Creates an instance of Agent. @@ -41,14 +40,6 @@ export default class Agent extends Startable.Controller { ) { super(); this.backup = new AgentBackup(this); - // this.pollux = new Pollux(apollo, castor); - this.pollux = new Pollux({ - Apollo: this.apollo, - Castor: this.castor, - Pluto: this.pluto, - Seed: this.seed, - Api: this.api, - }); } /** @@ -103,8 +94,14 @@ export default class Agent extends Startable.Controller { return this.runTask(task); } - isCredentialRevoked(credential: Domain.Credential) { - return this.pollux.handle("revocation-check", "prism/jwt", credential); + async isCredentialRevoked(credential: Domain.Credential): Promise { + const result = await this.runTask(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential } + })); + + return result.data; } private runTask(task: Task) { @@ -113,7 +110,6 @@ export default class Agent extends Startable.Controller { Apollo: this.apollo, Castor: this.castor, Pluto: this.pluto, - Pollux: this.pollux, Seed: this.seed, }); diff --git a/src/edge-agent/connectionsManager/ConnectionsManager.ts b/src/edge-agent/connectionsManager/ConnectionsManager.ts index c02e37f32..a40f85198 100644 --- a/src/edge-agent/connectionsManager/ConnectionsManager.ts +++ b/src/edge-agent/connectionsManager/ConnectionsManager.ts @@ -1,7 +1,4 @@ import { DID, Message, MessageDirection } from "../../domain"; -import { Castor } from "../../domain/buildingBlocks/Castor"; -import { Mercury } from "../../domain/buildingBlocks/Mercury"; -import { Pluto } from "../../domain/buildingBlocks/Pluto"; import { DIDPair } from "../../domain/models/DIDPair"; import { AgentError } from "../../domain/models/Errors"; import { AgentMessageEvents } from "../Agent.MessageEvents"; @@ -9,7 +6,6 @@ import { CancellableTask } from "../helpers/Task"; import { AgentMessageEvents as AgentMessageEventsClass, AgentOptions, - ConnectionsManager as ConnectionsManagerClass, ListenerKey, MediatorHandler, } from "../types"; @@ -17,8 +13,7 @@ import { ProtocolType } from "../protocols/ProtocolTypes"; import { RevocationNotification } from "../protocols/revocation/RevocationNotfiication"; import { IssueCredential } from "../protocols/issueCredential/IssueCredential"; import { HandleIssueCredential } from "../didcomm/HandleIssueCredential"; -import { Task } from "../../utils/tasks"; -import PlugPol from "../../pollux"; +import DIDCommAgent from "../didcomm/Agent"; /** @@ -29,7 +24,7 @@ import PlugPol from "../../pollux"; * @class ConnectionsManager * @typedef {ConnectionsManager} */ -export class ConnectionsManager implements ConnectionsManagerClass { +export class ConnectionsManager { /** * An array with cancellable tasks, mainly used to store one or multiple didcomm * connections in storage implementation at the same time. All of them can be cancelled @@ -58,6 +53,8 @@ export class ConnectionsManager implements ConnectionsManagerClass { */ public events: AgentMessageEventsClass; + public pairings: DIDPair[] = []; + /** * Creates an instance of ConnectionsManager. * @@ -69,17 +66,16 @@ export class ConnectionsManager implements ConnectionsManagerClass { * @param {DIDPair[]} [pairings=[]] */ constructor( - public castor: Castor, - public mercury: Mercury, - public pluto: Pluto, - public pollux: PlugPol, - public mediationHandler: MediatorHandler, - public pairings: DIDPair[] = [], + private readonly agent: DIDCommAgent, public options?: AgentOptions ) { this.events = new AgentMessageEvents(); } + get mediationHandler(): MediatorHandler { + return this.agent.mediationHandler; + } + get withWebsocketsExperiment() { return this.options?.experiments?.liveMode === true; } @@ -93,7 +89,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { */ async startMediator(): Promise { const mediationHandler = - await this.mediationHandler.bootRegisteredMediator(); + await this.agent.mediationHandler.bootRegisteredMediator(); if (!mediationHandler) { throw new AgentError.NoMediatorAvailableError(); @@ -121,7 +117,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { */ async awaitMessageResponse(id: string): Promise { console.log("Deprecated, use agent.addListener('THREAD-{{Your thread || messageId}}', fn), this method does not support live-mode."); - const messages = await this.mediationHandler.pickupUnreadMessages(10); + const messages = await this.agent.mediationHandler.pickupUnreadMessages(10); return messages.find(x => x.message.thid === id)?.message; } @@ -134,7 +130,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { attachmentId: string; message: Message; }[] = []): Promise { - if (!this.mediationHandler.mediator) { + if (!this.agent.mediationHandler.mediator) { throw new AgentError.NoMediatorAvailableError(); } @@ -144,11 +140,11 @@ export class ConnectionsManager implements ConnectionsManagerClass { const messageIds = received.map(x => x.attachmentId); if (messages.length > 0) { - await this.pluto.storeMessages(messages); + await this.agent.pluto.storeMessages(messages); } const revokeMessages = messages.filter(x => x.piuri === ProtocolType.PrismRevocation); - const allMessages = await this.pluto.getAllMessages(); + const allMessages = await this.agent.pluto.getAllMessages(); for (const message of revokeMessages) { const revokeMessage = RevocationNotification.fromMessage(message); @@ -162,17 +158,17 @@ export class ConnectionsManager implements ConnectionsManagerClass { if (matchingMessages.length > 0) { for (const message of matchingMessages) { const issueCredential = IssueCredential.fromMessage(message); - const ctx = Task.Context.make({ Pluto: this.pluto, Pollux: this.pollux }); + // const ctx = Task.Context.make({ Pluto: this.agent.pluto, Pollux: this.pollux }); const task = new HandleIssueCredential({ issueCredential }); - const credential = await ctx.run(task); + const credential = await (this.agent as any).runTask(task); - await this.pluto.revokeCredential(credential); + await this.agent.pluto.revokeCredential(credential); this.events.emit(ListenerKey.REVOKE, credential); } } } if (messageIds.length) { - await this.mediationHandler.registerMessagesAsRead(messageIds); + await this.agent.mediationHandler.registerMessagesAsRead(messageIds); } this.events.emit(ListenerKey.MESSAGE, messages); @@ -194,7 +190,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { } const storeDIDPairTask = new CancellableTask(async () => { - await this.pluto.storeDIDPair(paired.host, paired.receiver, paired.name); + await this.agent.pluto.storeDIDPair(paired.host, paired.receiver, paired.name); this.events.emit(ListenerKey.CONNECTION, paired); return paired; }); @@ -242,7 +238,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { * @returns {Promise} */ async registerMediator(hostDID: DID): Promise { - await this.mediationHandler.achieveMediation(hostDID); + await this.agent.mediationHandler.achieveMediation(hostDID); } /** @@ -254,8 +250,8 @@ export class ConnectionsManager implements ConnectionsManagerClass { */ async sendMessage(message: Message): Promise { message.direction = MessageDirection.SENT; - await this.pluto.storeMessage(message); - return this.mercury.sendMessageParseMessage(message); + await this.agent.pluto.storeMessage(message); + return this.agent.mercury.sendMessageParseMessage(message); } /** @@ -264,11 +260,11 @@ export class ConnectionsManager implements ConnectionsManagerClass { * @param {number} iterationPeriod */ async startFetchingMessages(iterationPeriod: number): Promise { - if (this.cancellable || !this.mediationHandler.mediator) { + if (this.cancellable || !this.agent.mediationHandler.mediator) { return; } - const currentMediator = this.mediationHandler.mediator.mediatorDID; - const resolvedMediator = await this.castor.resolveDID(currentMediator.toString()); + const currentMediator = this.agent.mediationHandler.mediator.mediatorDID; + const resolvedMediator = await this.agent.castor.resolveDID(currentMediator.toString()); const hasWebsocket = resolvedMediator.services.find(({ serviceEndpoint: { uri } }) => ( uri.startsWith("ws://") || @@ -277,7 +273,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { ); if (hasWebsocket && this.withWebsocketsExperiment) { this.cancellable = new CancellableTask(async (signal) => { - this.mediationHandler.listenUnreadMessages( + this.agent.mediationHandler.listenUnreadMessages( signal, hasWebsocket.serviceEndpoint.uri, (messages) => this.processMessages(messages) @@ -286,7 +282,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { } else { const timeInterval = Math.max(iterationPeriod, 5) * 1000; this.cancellable = new CancellableTask(async () => { - const unreadMessages = await this.mediationHandler.pickupUnreadMessages(10); + const unreadMessages = await this.agent.mediationHandler.pickupUnreadMessages(10); await this.processMessages(unreadMessages); }, timeInterval); } diff --git a/src/edge-agent/didcomm/Agent.ts b/src/edge-agent/didcomm/Agent.ts index ae8f9f54c..995a59301 100644 --- a/src/edge-agent/didcomm/Agent.ts +++ b/src/edge-agent/didcomm/Agent.ts @@ -27,7 +27,6 @@ import { HandleOfferCredential } from "./HandleOfferCredential"; import { HandlePresentation } from "./HandlePresentation"; import { CreatePresentation } from "./CreatePresentation"; import { ProtocolType } from "../protocols/ProtocolTypes"; -import Pollux from "../../pollux"; import Apollo from "../../apollo"; import Castor from "../../castor"; import * as DIDfns from "../didFunctions"; @@ -38,19 +37,12 @@ import { ParseInvitation } from "./ParseInvitation"; import { HandleOOBInvitation } from "./HandleOOBInvitation"; import { Startable } from "../../domain/protocols/Startable"; import { notNil } from "../../utils"; -import PlugPol from "../../pollux"; -import JWTModule from "../../pollux/utils/jwt"; -import OEAModule from "../../pollux/plugins/oea"; -import DIFModule from "../../pollux/plugins/dif"; -import { Plugin } from "../../plugins"; import { RevealCredentialFields } from "../helpers/RevealCredentialFields"; - -enum AgentState { - STOPPED = "stopped", - STARTING = "starting", - RUNNING = "running", - STOPPING = "stopping", -} +import { RunProtocol } from "../helpers/RunProtocol"; +import { asJsonObj, expect } from "../../utils"; +import { PluginManager } from "../../plugins"; +import OEAPlugin from "../../plugins/internal/oea"; +import DIFPlugin from "../../plugins/internal/dif"; /** * Edge agent implementation @@ -61,7 +53,10 @@ enum AgentState { */ export default class DIDCommAgent extends Startable.Controller { public backup: AgentBackup; - public readonly pollux: Domain.Pollux; + public readonly plugins: PluginManager; + + public readonly connectionManager: ConnectionsManager; + public readonly mediationHandler: MediatorHandler; /** @@ -76,22 +71,21 @@ export default class DIDCommAgent extends Startable.Controller { public readonly castor: Domain.Castor, public readonly pluto: Domain.Pluto, public readonly mercury: Domain.Mercury, - public readonly mediationHandler: MediatorHandler, - public readonly connectionManager: ConnectionsManager, public readonly seed: Domain.Seed = apollo.createRandomSeed().seed, public readonly api: Domain.Api = new FetchApi(), options?: AgentOptions ) { super(); this.backup = new AgentBackup(this); - // ? tmp hack around before connectionManager refactor - this.pollux = connectionManager.pollux; + const store = new PublicMediatorStore(pluto); + const mediatorDID = expect(options?.mediatorDID); + this.mediationHandler = new BasicMediatorHandler(mediatorDID, mercury, store); + this.connectionManager = new ConnectionsManager(this, options); - if (this.pollux instanceof PlugPol) { - this.pollux.register(JWTModule); - this.pollux.register(DIFModule); - this.pollux.register(OEAModule); - } + this.backup = new AgentBackup(this); + this.plugins = new PluginManager(); + this.plugins.register(DIFPlugin); + this.plugins.register(OEAPlugin); } /** @@ -126,66 +120,30 @@ export default class DIDCommAgent extends Startable.Controller { const castor = params.castor ?? new Castor(apollo); const didcomm = new DIDCommWrapper(apollo, castor, pluto); const mercury = params.mercury ?? new Mercury(castor, didcomm, api); - const store = new PublicMediatorStore(pluto); - const handler = new BasicMediatorHandler(mediatorDID, mercury, store); const seed = params.seed ?? apollo.createRandomSeed().seed; - const pollux = new Pollux({ - Api: api, - Apollo: apollo, - Castor: castor, - Mercury: mercury, - Pluto: pluto, - Seed: seed, - }); - - const manager = new ConnectionsManager( - castor, - mercury, - pluto, - pollux, - handler, - [], - params.options - ); - const agent = new DIDCommAgent( apollo, castor, pluto, mercury, - handler, - manager, seed, api, - params.options + { mediatorDID, ...asJsonObj(params.options) } ); return agent; } - /** - * Add a plugin module to the executable scope - * - * @param plugin - */ - register(plugin: Plugin) { - if (this.pollux instanceof PlugPol) { - this.pollux.register(plugin); - } - } - /** * Asyncronously start the agent * * @async * @returns {Promise} */ - // async start(): Promise { protected async _start() { try { await this.pluto.start(); - // await this.pollux.start(); await this.connectionManager.startMediator(); } catch (e) { @@ -198,7 +156,7 @@ export default class DIDCommAgent extends Startable.Controller { } } - if (this.connectionManager.mediationHandler.mediator !== undefined) { + if (this.mediationHandler.mediator !== undefined) { await this.connectionManager.startFetchingMessages(5); } else { @@ -255,15 +213,16 @@ export default class DIDCommAgent extends Startable.Controller { const ctx = Task.Context.make({ ConnectionManager: this.connectionManager, MediationHandler: this.mediationHandler, + Plugins: this.plugins, Mercury: this.mercury, Api: this.api, Apollo: this.apollo, Castor: this.castor, Pluto: this.pluto, - Pollux: this.pollux, Seed: this.seed, }); + ctx.extend(this.plugins.getModules()); return ctx.run(task); } @@ -456,8 +415,15 @@ export default class DIDCommAgent extends Startable.Controller { * @param credential * @returns */ - isCredentialRevoked(credential: Domain.Credential) { - return this.pollux.handle("revocation-check", "prism/jwt", credential); + async isCredentialRevoked(credential: Domain.Credential): Promise { + const task = new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential } + }); + + const result = await this.runTask(task); + return result.data; } /** diff --git a/src/edge-agent/didcomm/Context.ts b/src/edge-agent/didcomm/Context.ts index b68fe96f1..12b3c7131 100644 --- a/src/edge-agent/didcomm/Context.ts +++ b/src/edge-agent/didcomm/Context.ts @@ -1,3 +1,4 @@ +import { PluginManager } from "../../plugins"; import { Task } from "../../utils/tasks"; import { ConnectionsManager } from "../connectionsManager/ConnectionsManager"; import { MediatorHandler } from "../types"; @@ -5,4 +6,5 @@ import { MediatorHandler } from "../types"; export type DIDCommContext = Task.Context<{ ConnectionManager: ConnectionsManager; MediationHandler: MediatorHandler; + Plugins: PluginManager; }>; diff --git a/src/edge-agent/didcomm/CreatePresentation.ts b/src/edge-agent/didcomm/CreatePresentation.ts index fff399978..29098729a 100644 --- a/src/edge-agent/didcomm/CreatePresentation.ts +++ b/src/edge-agent/didcomm/CreatePresentation.ts @@ -4,6 +4,7 @@ import { Presentation, RequestPresentation } from "../protocols/proofPresentatio import { DIDCommContext } from "./Context"; import { Task } from "../../utils/tasks"; import { expect, isString } from "../../utils"; +import { RunProtocol } from "../helpers/RunProtocol"; /** * Asyncronously create a verifiablePresentation from a valid stored verifiableCredential @@ -20,13 +21,14 @@ export class CreatePresentation extends Task { async run(ctx: DIDCommContext) { const { credential, request } = this.args; const attachment = expect(request.attachments.at(0)); + const format = expect(attachment.format, "Invalid attachment format"); const presentationRequest = attachment.payload; - const payload = await ctx.Pollux.handle( - "presentation-request", - attachment.format, - { presentationRequest, credential }, - ); + const payload = await ctx.run(new RunProtocol({ + type: "presentation-request", + pid: format, + data: { presentationRequest, credential } + })); // TODO why are we converting to string / base64 attachment here const proof = isString(payload.data) ? payload.data : JSON.stringify(payload.data); diff --git a/src/edge-agent/didcomm/CreatePresentationRequest.ts b/src/edge-agent/didcomm/CreatePresentationRequest.ts index 6e8a727de..dbb81bf04 100644 --- a/src/edge-agent/didcomm/CreatePresentationRequest.ts +++ b/src/edge-agent/didcomm/CreatePresentationRequest.ts @@ -4,6 +4,11 @@ import { RequestPresentation } from "../protocols/proofPresentation"; import { CreatePeerDID } from "./CreatePeerDID"; import { Task } from "../../utils/tasks"; import { DIDCommContext } from "./Context"; +import { Context as ACContext } from "../../plugins/internal/anoncreds"; +import { Context as DIFContext } from "../../plugins/internal/dif"; + +// TODO tmp workaround using plugins in Agent task +type TaskContext = DIDCommContext & ACContext & DIFContext; interface Args { type: Domain.CredentialType; @@ -12,7 +17,7 @@ interface Args { } export class CreatePresentationRequest extends Task { - async run(ctx: DIDCommContext) { + async run(ctx: TaskContext) { // TODO temp workaround until functions removed const { claims, toDID, type } = this.args; const didDocument = await ctx.Castor.resolveDID(toDID.toString()); diff --git a/src/edge-agent/didcomm/HandleIssueCredential.ts b/src/edge-agent/didcomm/HandleIssueCredential.ts index 135f61510..deba39d60 100644 --- a/src/edge-agent/didcomm/HandleIssueCredential.ts +++ b/src/edge-agent/didcomm/HandleIssueCredential.ts @@ -1,6 +1,7 @@ import * as Domain from "../../domain"; import { expect } from "../../utils"; import { Task } from "../../utils/tasks"; +import { RunProtocol } from "../helpers/RunProtocol"; import { IssueCredential } from "../protocols/issueCredential/IssueCredential"; import { DIDCommContext } from "./Context"; @@ -16,14 +17,16 @@ export class HandleIssueCredential extends Task { async run(ctx: DIDCommContext) { const { issueCredential } = this.args; const attachment = expect(issueCredential.attachments.at(0), "Invalid attachment"); - const result = await ctx.Pollux.handle( - "credential-issue", - attachment.format, - { + const format = expect(attachment.format, "Invalid attachment"); + const result = await ctx.run(new RunProtocol({ + type: "credential-issue", + pid: format, + // TODO flatten data and move thid to context + data: { data: attachment.payload, thid: issueCredential.thid } - ); + })); const credential = result.pid === "credential" ? result.data diff --git a/src/edge-agent/didcomm/HandleOfferCredential.ts b/src/edge-agent/didcomm/HandleOfferCredential.ts index c0e1b3d67..8e7ea3073 100644 --- a/src/edge-agent/didcomm/HandleOfferCredential.ts +++ b/src/edge-agent/didcomm/HandleOfferCredential.ts @@ -5,6 +5,7 @@ import { RequestCredential } from "../protocols/issueCredential/RequestCredentia import { Task } from "../../utils/tasks"; import { DIDCommContext } from "./Context"; import { expect, isString } from "../../utils"; +import { RunProtocol } from "../helpers/RunProtocol"; /** * Asyncronously prepare a request credential message from a valid offerCredential @@ -19,15 +20,16 @@ export class HandleOfferCredential extends Task { async run(ctx: DIDCommContext) { const { offer } = this.args; const attachment = expect(offer.attachments.at(0), "Invalid attachment"); + const format = expect(attachment.format, "Invalid attachment format"); - const result = await ctx.Pollux.handle( - "credential-offer", - attachment.format, - { + const result = await ctx.run(new RunProtocol({ + type: "credential-offer", + pid: format, + data: { offer: attachment.payload, thid: offer.thid } - ); + })); // ?? can this all move to send const from = expect(offer.to, 'Missing "from"'); diff --git a/src/edge-agent/didcomm/HandlePresentation.ts b/src/edge-agent/didcomm/HandlePresentation.ts index 93a3f1cbb..21a6374a6 100644 --- a/src/edge-agent/didcomm/HandlePresentation.ts +++ b/src/edge-agent/didcomm/HandlePresentation.ts @@ -1,5 +1,7 @@ import * as Domain from "../../domain"; +import { expect } from "../../utils"; import { Task } from "../../utils/tasks"; +import { RunProtocol } from "../helpers/RunProtocol"; import { Presentation } from "../protocols/proofPresentation"; import { ProtocolType } from "../protocols/ProtocolTypes"; import { DIDCommContext } from "./Context"; @@ -11,26 +13,28 @@ interface Args { export class HandlePresentation extends Task { async run(ctx: DIDCommContext) { const { presentation } = this.args; - const attachment = presentation.attachments.at(0); - if (!attachment) { - throw new Domain.AgentError.UnsupportedAttachmentType("Invalid presentation message, attachment missing"); - } + const attachment = expect( + presentation.attachments.at(0), + new Domain.AgentError.UnsupportedAttachmentType("Invalid presentation message, attachment missing") + ); + const format = expect(attachment.format, "Invalid attachment format"); + if (!presentation.thid) { throw new Domain.AgentError.UnsupportedAttachmentType("Cannot find any message with that threadID"); } const presReq = await this.getPresentationRequest(ctx, presentation.thid); - const verified = await ctx.Pollux.handle( - "presentation-verify", - attachment.format, - { + const verified = await ctx.run(new RunProtocol({ + type: "presentation-verify", + pid: format, + data: { presentation: attachment.payload, presentationRequest: presReq, thid: presentation.thid, } - ); + })); return verified.data; } diff --git a/src/edge-agent/helpers/RevealCredentialFields.ts b/src/edge-agent/helpers/RevealCredentialFields.ts index 00cc58e96..28af890f7 100644 --- a/src/edge-agent/helpers/RevealCredentialFields.ts +++ b/src/edge-agent/helpers/RevealCredentialFields.ts @@ -1,4 +1,5 @@ import * as Domain from "../../domain"; +import { Plugins } from "../../plugins"; import { AnonCredsCredential } from "../../pollux/models/AnonCredsVerifiableCredential"; import { JWTCredential } from "../../pollux/models/JWTVerifiableCredential"; import { SDJWTCredential } from "../../pollux/models/SDJWTVerifiableCredential"; @@ -9,8 +10,8 @@ interface Args { fields: string[]; } -export class RevealCredentialFields extends Task<{}, Args> { - async run(ctx: Task.Context): Promise<{}> { +export class RevealCredentialFields extends Task { + async run(ctx: Plugins.Context) { if (this.args.credential instanceof JWTCredential) { return this.runJWT(); } @@ -20,7 +21,7 @@ export class RevealCredentialFields extends Task<{}, Args> { } if (this.args.credential instanceof AnonCredsCredential) { - return this.runAnoncreds(ctx); + return this.runAnoncreds(); } throw new Error("unhandled credential"); @@ -31,7 +32,7 @@ export class RevealCredentialFields extends Task<{}, Args> { return claim; } - async runSDJWT(ctx: Task.Context) { + async runSDJWT(ctx: Plugins.Context) { const credential = this.args.credential; let disclosedClaims: JsonObj = {}; @@ -46,7 +47,7 @@ export class RevealCredentialFields extends Task<{}, Args> { return disclosedClaims; } - async runAnoncreds(ctx: Task.Context) { + async runAnoncreds() { const credential = this.args.credential; const revealed = this.args.fields.reduce((acc, field) => { diff --git a/src/edge-agent/helpers/RunProtocol.ts b/src/edge-agent/helpers/RunProtocol.ts new file mode 100644 index 000000000..84c2d1452 --- /dev/null +++ b/src/edge-agent/helpers/RunProtocol.ts @@ -0,0 +1,65 @@ +import { Payload } from "../../domain/protocols/Payload"; +import { JsonObj, isObject, notEmptyString, notNil } from "../../utils"; +import { Task } from "../../utils/tasks"; +import { DIDCommContext } from "../didcomm/Context"; +import { JWT, SDJWT } from "../../pollux/utils/jwt"; +import { Plugins } from "../../plugins"; +import { Domain } from "../.."; + +interface IArgs { + type: T; + pid: string; + data: D; +} + +type Args_CredentialIssue = IArgs<"credential-issue", { + data: any; + thid?: string; +}>; +type Args_CredentialOffer = IArgs<"credential-offer", { + offer: any; + thid?: string; +}>; +type Args_PresentationRequest = IArgs<"presentation-request", { + credential: Domain.Credential; + presentationRequest: any; +}>; +type Args_PresentationVerify = IArgs<"presentation-verify", { + presentation: any; + presentationRequest: any; + thid?: string; +}>; +type Args_RevocationCheck = IArgs<"revocation-check", { credential: Domain.Credential; }>; + +type Args = + | Args_CredentialIssue + | Args_CredentialOffer + | Args_PresentationRequest + | Args_PresentationVerify + | Args_RevocationCheck; + +export class RunProtocol extends Task { + async run(ctx: DIDCommContext) { + const protocolCtor = ctx.Plugins.findProtocol(this.args.type, this.args.pid); + + const internalModules: Plugins.InternalModules = { + JWT: new JWT(), + SDJWT: new SDJWT(), + }; + + ctx.extend(internalModules); + + const task = new protocolCtor(this.args.data); + const result = await ctx.run(task); + this.assertPayload(result); + return result; + } + + private assertPayload(value: unknown): asserts value is Payload { + if (isObject(value) && notEmptyString(value.pid) && notNil(value.data)) { + return; + } + + throw new Error("invalid payload returned"); + } +} diff --git a/src/edge-agent/oidc/Agent.ts b/src/edge-agent/oidc/Agent.ts index 7fc6a0eb3..3a200b9a9 100644 --- a/src/edge-agent/oidc/Agent.ts +++ b/src/edge-agent/oidc/Agent.ts @@ -1,7 +1,6 @@ import * as Domain from "../../domain"; import Apollo from "../../apollo"; import Castor from "../../castor"; -import Pollux from "../../pollux"; import { OIDC } from "./types"; import { AuthorizationRequest } from "./protocols/AuthorizationRequest"; import { TokenResponse } from "./protocols/TokenResponse"; @@ -15,6 +14,10 @@ import * as Tasks from "./tasks"; import * as Errors from "./errors"; import { JsonObj, expect, notNil } from "../../utils"; import { RevealCredentialFields } from "../helpers/RevealCredentialFields"; +import { RunProtocol } from "../helpers/RunProtocol"; +import { PluginManager } from "../../plugins"; +import OEAPlugin from "../../plugins/internal/oea"; +import DIFPlugin from "../../plugins/internal/dif"; /** * https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html @@ -31,7 +34,7 @@ class Connection { export class OIDCAgent extends Startable.Controller { private connections: Connection[] = []; - public readonly pollux: Pollux; + public readonly plugins: PluginManager; constructor( public readonly apollo: Domain.Apollo, @@ -43,13 +46,9 @@ export class OIDCAgent extends Startable.Controller { super(); this.seed = seed ?? apollo.createRandomSeed().seed; this.api = api ?? new FetchApi(); - this.pollux = new Pollux({ - Apollo: this.apollo, - Castor: this.castor, - Pluto: this.pluto, - Seed: this.seed, - Api: this.api, - }); + this.plugins = new PluginManager(); + this.plugins.register(DIFPlugin); + this.plugins.register(OEAPlugin); } /** @@ -97,7 +96,6 @@ export class OIDCAgent extends Startable.Controller { Apollo: this.apollo, Castor: this.castor, Pluto: this.pluto, - Pollux: this.pollux, Seed: this.seed, }); @@ -109,8 +107,14 @@ export class OIDCAgent extends Startable.Controller { * @param credential * @returns */ - isCredentialRevoked(credential: Domain.Credential) { - return this.pollux.handle("revocation-check", "prism/jwt", credential); + async isCredentialRevoked(credential: Domain.Credential): Promise { + const result = await this.runTask(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential } + })); + + return result.data; } /** diff --git a/src/edge-agent/oidc/tasks/HandleCredentialRequest.ts b/src/edge-agent/oidc/tasks/HandleCredentialRequest.ts index 5e4b3cada..dbfe7ae5a 100644 --- a/src/edge-agent/oidc/tasks/HandleCredentialRequest.ts +++ b/src/edge-agent/oidc/tasks/HandleCredentialRequest.ts @@ -20,8 +20,7 @@ export class HandleCredentialRequest extends Task { ); validate(response.body, TB.Object({ credential: TB.String() })); - // const rawCred = Buffer.from(response.body.credential); - // const credential = await ctx.Pollux.parseCredential(rawCred, { type: Domain.CredentialType.JWT }); + const credential = JWTCredential.fromJWS(response.body.credential); return credential as Domain.Credential; diff --git a/src/edge-agent/types/index.ts b/src/edge-agent/types/index.ts index ca78eb2af..8a93e7eac 100644 --- a/src/edge-agent/types/index.ts +++ b/src/edge-agent/types/index.ts @@ -23,6 +23,7 @@ export enum InvitationTypes { export type AgentOptions = { + mediatorDID?: DID; experiments?: { liveMode?: boolean; }; diff --git a/src/index.ts b/src/index.ts index 9d57bee60..86f39be34 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,6 @@ export { default as Castor } from "./castor/Castor"; export * as Domain from "./domain"; export { default as Mercury } from "./mercury/Mercury"; export * from "./pluto"; -export { PlugPol as Pollux } from "./pollux/PlugPol"; // alias DIDCommAgent as Agent to hide breaking changes export { default as Agent } from "./edge-agent/didcomm/Agent"; @@ -47,12 +46,13 @@ export * from "./apollo/utils/Ed25519KeyPair"; export * from "./apollo/utils/X25519PrivateKey"; export * from "./apollo/utils/X25519PublicKey"; export * from "./apollo/utils/X25519KeyPair"; -export * from "./pollux/models/AnonCredsVerifiableCredential"; -export * from "./pollux/models/JWTVerifiableCredential"; -export * from "./pollux/models/SDJWTVerifiableCredential"; + export { KeyProperties } from "./domain/models/KeyProperties"; +export { JWTCredential, JWTVerifiableCredentialRecoveryId } from "./pollux/models/JWTVerifiableCredential"; +export { SDJWTCredential, SDJWTVerifiableCredentialRecoveryId } from "./pollux/models/SDJWTVerifiableCredential"; +export * from "./pollux/models/AnonCredsVerifiableCredential"; -import AnoncredsModule from "./pollux/plugins/anoncreds/Plugin"; -export const Modules = { - Anoncreds: AnoncredsModule +import AnoncredsPlugin from "./plugins/internal/anoncreds"; +export const Plugins = { + Anoncreds: AnoncredsPlugin }; diff --git a/src/mercury/didcomm/Wrapper.ts b/src/mercury/didcomm/Wrapper.ts index 58126f321..8cb1c57a3 100644 --- a/src/mercury/didcomm/Wrapper.ts +++ b/src/mercury/didcomm/Wrapper.ts @@ -7,7 +7,7 @@ import type { Attachment, AttachmentData, } from "didcomm-wasm"; -import wasmBuffer from "didcomm-wasm/didcomm_js_bg.wasm" +import wasmBuffer from "didcomm-wasm/didcomm_js_bg.wasm"; import * as Domain from "../../domain"; import { DIDCommDIDResolver } from "./DIDResolver"; @@ -38,7 +38,7 @@ export class DIDCommWrapper implements DIDCommProtocol { await module.default(wasmInstance); return module; }); - return this.didcomm!; + return this.didcomm; } private doesRequireReturnRoute(type: string) { diff --git a/src/plugins/Plugin.ts b/src/plugins/Plugin.ts new file mode 100644 index 000000000..051b93a70 --- /dev/null +++ b/src/plugins/Plugin.ts @@ -0,0 +1,36 @@ +import { Task } from "../utils"; +import { asArray } from "../utils/guards"; +import { Arrayable, Ctor } from "../utils/types"; + +/** + * Provide interface to augment the SDK. + * + * Currently able to: + * - register protocol handlers + * - extend the running context + */ +export class Plugin { + private readonly _extensions = new Map(); + public readonly tasks = new Map>>(); + + get extensions() { + return Object.fromEntries(this._extensions.entries()); + } + + // extend Context with + addModule(key: string, module: any): this { + this._extensions.set(key, module); + return this; + } + + // addMessageHandler() {} + + // addRevocationMethod() {} + + // register a protocol + register(pids: Arrayable, task: Ctor>): this { + const pidsArr = asArray(pids); + pidsArr.forEach(key => this.tasks.set(key, task)); + return this; + } +} diff --git a/src/plugins/PluginManager.ts b/src/plugins/PluginManager.ts new file mode 100644 index 000000000..c9268fbed --- /dev/null +++ b/src/plugins/PluginManager.ts @@ -0,0 +1,30 @@ +import { notNil } from "../utils"; +import { Plugin } from "."; + +export class PluginManager { + private readonly plugins: Plugin[] = []; + + register(plugin: Plugin) { + this.plugins.push(plugin); + } + + getModules() { + const modules = this.plugins + .map(x => x.extensions) + .reduce((acc, x) => Object.assign(acc, x), {}); + + return modules; + } + + findProtocol(type: string, id: string) { + for (const plugin of this.plugins) { + const protocol = plugin.tasks.get(id) ?? plugin.tasks.get(`${type}/${id}`); + + if (notNil(protocol)) { + return protocol; + } + } + + throw new Error(`Protocol handler not found for ${id} (${type})`); + } +} diff --git a/src/plugins/index.ts b/src/plugins/index.ts index c7f091233..af37fd99a 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -1,42 +1,3 @@ -import { Task } from "../utils"; -import { asArray } from "../utils/guards"; -import { Arrayable, Ctor, JsonObj, Normalize } from "../utils/types"; - -/** - * Provide interface to augment the SDK. - * - * Currently able to: - * - register protocol handlers - * - extend the executable context - */ -export class Plugin { - private readonly _extensions = new Map(); - public readonly tasks = new Map>>(); - - get extensions() { - return Object.fromEntries(this._extensions.entries()); - } - - - // addMessageHandler() {} - - // addRevocationMethod() {} - - - // extend Context - extend(key: K, extension: E): Plugin> { - this._extensions.set(key, extension); - return this; - } - - // register a protocol - register(pids: Arrayable, task: Ctor>) { - const pidsArr = asArray(pids); - pidsArr.forEach(key => this.tasks.set(key, task)); - } -} - -export namespace Plugin { - // flatten intersection to appear as single interface - export type ExtractExtension = T extends Plugin ? X : {}; -} +export * from "./types"; +export * from "./Plugin"; +export * from "./PluginManager"; diff --git a/src/pollux/plugins/anoncreds/CredentialIssue.ts b/src/plugins/internal/anoncreds/CredentialIssue.ts similarity index 86% rename from src/pollux/plugins/anoncreds/CredentialIssue.ts rename to src/plugins/internal/anoncreds/CredentialIssue.ts index bef4f215f..20ab3bcf4 100644 --- a/src/pollux/plugins/anoncreds/CredentialIssue.ts +++ b/src/plugins/internal/anoncreds/CredentialIssue.ts @@ -1,11 +1,12 @@ import * as Domain from "../../../domain"; import { Payload } from "../../../domain/protocols/Payload"; -import { AnonCredsCredential } from "../../models/AnonCredsVerifiableCredential"; +import { AnonCredsCredential } from "../../../pollux/models/AnonCredsVerifiableCredential"; import { expect } from "../../../utils"; -import { Pollux } from "../../PlugPol"; import { GetLinkSecret } from "./GetLinkSecret"; import { FetchCredentialDefinition } from "./FetchCredentialDefinition"; +import type { Context } from "./index"; import * as Types from "./types"; +import { Plugins } from "../../../plugins"; export interface Args { /** @@ -18,8 +19,8 @@ export interface Args { thid?: string; } -export class CredentialIssue extends Pollux.Task { - async run(ctx: Pollux.Context) { +export class CredentialIssue extends Plugins.Task { + async run(ctx: Context) { const thid = expect(this.args.thid, "Thread ID is required"); const metadata = await ctx.Pluto.getCredentialMetadata(thid); diff --git a/src/pollux/plugins/anoncreds/CredentialOffer.ts b/src/plugins/internal/anoncreds/CredentialOffer.ts similarity index 86% rename from src/pollux/plugins/anoncreds/CredentialOffer.ts rename to src/plugins/internal/anoncreds/CredentialOffer.ts index ce1abb691..36dcddd6d 100644 --- a/src/pollux/plugins/anoncreds/CredentialOffer.ts +++ b/src/plugins/internal/anoncreds/CredentialOffer.ts @@ -1,18 +1,19 @@ import * as Domain from "../../../domain"; import { expect } from "../../../utils"; -import { Pollux } from "../../PlugPol"; import { Payload } from "../../../domain/protocols/Payload"; import { GetLinkSecret } from "./GetLinkSecret"; import { FetchCredentialDefinition } from "./FetchCredentialDefinition"; +import type { Context } from "./index"; import * as Types from "./types"; +import { Plugins } from "../../../plugins"; interface Args { offer: Types.CredentialOffer; thid?: string; } -export class CredentialOffer extends Pollux.Task { - async run(ctx: Pollux.Context) { +export class CredentialOffer extends Plugins.Task { + async run(ctx: Context) { const metaname = expect(this.args.thid, "Missing offer.thid"); const offer = this.args.offer; // TODO validate diff --git a/src/pollux/plugins/anoncreds/FetchCredentialDefinition.ts b/src/plugins/internal/anoncreds/FetchCredentialDefinition.ts similarity index 84% rename from src/pollux/plugins/anoncreds/FetchCredentialDefinition.ts rename to src/plugins/internal/anoncreds/FetchCredentialDefinition.ts index 9264ce1ff..9df32350c 100644 --- a/src/pollux/plugins/anoncreds/FetchCredentialDefinition.ts +++ b/src/plugins/internal/anoncreds/FetchCredentialDefinition.ts @@ -1,4 +1,5 @@ import { Task } from "../../../utils"; +import type { Context } from "./index"; import * as Types from "./types"; interface Args { @@ -6,7 +7,7 @@ interface Args { } export class FetchCredentialDefinition extends Task { - async run(ctx: Task.Context) { + async run(ctx: Context) { const response = await ctx.Api.request("GET", this.args.uri); // TODO validate return response.body as Types.CredentialDefinition; diff --git a/src/pollux/plugins/anoncreds/FetchSchema.ts b/src/plugins/internal/anoncreds/FetchSchema.ts similarity index 100% rename from src/pollux/plugins/anoncreds/FetchSchema.ts rename to src/plugins/internal/anoncreds/FetchSchema.ts diff --git a/src/pollux/plugins/anoncreds/GetLinkSecret.ts b/src/plugins/internal/anoncreds/GetLinkSecret.ts similarity index 90% rename from src/pollux/plugins/anoncreds/GetLinkSecret.ts rename to src/plugins/internal/anoncreds/GetLinkSecret.ts index 134a48d4c..bdf4abb31 100644 --- a/src/pollux/plugins/anoncreds/GetLinkSecret.ts +++ b/src/plugins/internal/anoncreds/GetLinkSecret.ts @@ -1,5 +1,6 @@ import { LinkSecret } from "../../../domain"; import { Task, notNil } from "../../../utils"; +import type { Context } from "./index"; /** * Retrieve a LinkSecret for use with Anoncreds operations @@ -7,7 +8,7 @@ import { Task, notNil } from "../../../utils"; * or a newly created one if none found */ export class GetLinkSecret extends Task { - async run(ctx: Task.Context) { + async run(ctx: Context) { const linkSecret = await ctx.Pluto.getLinkSecret(); if (notNil(linkSecret)) { diff --git a/src/pollux/plugins/anoncreds/PresentationRequest.ts b/src/plugins/internal/anoncreds/PresentationRequest.ts similarity index 54% rename from src/pollux/plugins/anoncreds/PresentationRequest.ts rename to src/plugins/internal/anoncreds/PresentationRequest.ts index c4bfb6680..2ecdde9ff 100644 --- a/src/pollux/plugins/anoncreds/PresentationRequest.ts +++ b/src/plugins/internal/anoncreds/PresentationRequest.ts @@ -1,9 +1,12 @@ +import { InvalidPresentationDefinitionError } from "../../../domain/models/errors/Pollux"; import { Payload } from "../../../domain/protocols/Payload"; -import { AnonCredsCredential } from "../../models/AnonCredsVerifiableCredential"; -import { Pollux } from "../../PlugPol"; +import { Plugins } from "../../../plugins"; +import { AnonCredsCredential } from "../../../pollux/models/AnonCredsVerifiableCredential"; +import { isObject, notEmptyString } from "../../../utils"; import { FetchCredentialDefinition } from "./FetchCredentialDefinition"; import { fetchSchema } from "./FetchSchema"; import { GetLinkSecret } from "./GetLinkSecret"; +import type { Context } from "./index"; import * as Types from "./types"; interface Args { @@ -11,9 +14,10 @@ interface Args { presentationRequest: Types.PresentationRequest; } -export class PresentationRequest extends Pollux.Task { - async run(ctx: Pollux.Context) { +export class PresentationRequest extends Plugins.Task { + async run(ctx: Context) { // TODO validate Credential and PresentationRequest + this.validate(); const credential = this.args.credential; const presentationRequest = this.args.presentationRequest; const linkSecret = await ctx.run(new GetLinkSecret()); @@ -33,4 +37,21 @@ export class PresentationRequest extends Pollux.Task { // return JSON.stringify(result); return Payload.make(Types.PRESENTATION, result); } + + private validate() { + if (!(this.args.credential instanceof AnonCredsCredential)) { + throw new Error('Required a valid Anoncreds Credential for Anoncreds Presentation submission'); + } + + const request = this.args.presentationRequest; + const validRequest = isObject(request) + && notEmptyString(request.name) + && notEmptyString(request.nonce) + && isObject(request.requested_attributes) + && isObject(request.requested_predicates); + + if (!validRequest) { + throw new InvalidPresentationDefinitionError(); + } + } } diff --git a/src/pollux/plugins/anoncreds/PresentationVerify.ts b/src/plugins/internal/anoncreds/PresentationVerify.ts similarity index 91% rename from src/pollux/plugins/anoncreds/PresentationVerify.ts rename to src/plugins/internal/anoncreds/PresentationVerify.ts index 162444665..1fc67b94b 100644 --- a/src/pollux/plugins/anoncreds/PresentationVerify.ts +++ b/src/plugins/internal/anoncreds/PresentationVerify.ts @@ -1,11 +1,12 @@ import * as Domain from "../../../domain"; -import { Pollux } from "../../PlugPol"; -import * as Types from "./types"; import { ProtocolType } from "../../../edge-agent/protocols/ProtocolTypes"; import { FetchCredentialDefinition } from "./FetchCredentialDefinition"; import { fetchSchema } from "./FetchSchema"; import { Payload } from "../../../domain/protocols/Payload"; import { notNil } from "../../../utils"; +import type { Context } from "./index"; +import * as Types from "./types"; +import { Plugins } from "../../../plugins"; interface Args { presentation: Types.Presentation; @@ -13,8 +14,8 @@ interface Args { thid: string; } -export class PresentationVerify extends Pollux.Task { - async run(ctx: Pollux.Context) { +export class PresentationVerify extends Plugins.Task { + async run(ctx: Context) { const presentationSubmission = this.args.presentation; const presentationRequest = await this.getPresentationRequest(ctx); const isValidPresentation = await ctx.Anoncreds.isValidPresentation(presentationSubmission); @@ -43,7 +44,7 @@ export class PresentationVerify extends Pollux.Task { return Payload.make("valid", valid); } - private async getPresentationRequest(ctx: Pollux.Context) { + private async getPresentationRequest(ctx: Context) { if (notNil(this.args.presentationRequest)) { return this.args.presentationRequest; } diff --git a/src/plugins/internal/anoncreds/index.ts b/src/plugins/internal/anoncreds/index.ts new file mode 100644 index 000000000..5cd0e25b0 --- /dev/null +++ b/src/plugins/internal/anoncreds/index.ts @@ -0,0 +1,18 @@ +import { Plugin, Plugins } from "../../../plugins"; +import * as Types from "./types"; +import { AnoncredsLoader } from "./module/AnoncredsLoader"; +import { CredentialIssue } from "./CredentialIssue"; +import { CredentialOffer } from "./CredentialOffer"; +import { PresentationRequest } from "./PresentationRequest"; +import { PresentationVerify } from "./PresentationVerify"; + +export type Context = Plugins.Context<{ Anoncreds: AnoncredsLoader; }>; + +const plugin = new Plugin() + .addModule("Anoncreds", new AnoncredsLoader()) + .register(Types.CREDENTIAL_ISSUE, CredentialIssue) + .register(Types.CREDENTIAL_OFFER, CredentialOffer) + .register(Types.PRESENTATION, PresentationVerify) + .register(Types.PRESENTATION_REQUEST, PresentationRequest); + +export default plugin; diff --git a/src/pollux/plugins/anoncreds/AnoncredsLoader.ts b/src/plugins/internal/anoncreds/module/AnoncredsLoader.ts similarity index 98% rename from src/pollux/plugins/anoncreds/AnoncredsLoader.ts rename to src/plugins/internal/anoncreds/module/AnoncredsLoader.ts index b3286ed88..b0e0fd217 100644 --- a/src/pollux/plugins/anoncreds/AnoncredsLoader.ts +++ b/src/plugins/internal/anoncreds/module/AnoncredsLoader.ts @@ -1,7 +1,7 @@ import type * as Anoncreds from "anoncreds-wasm"; import wasmBuffer from 'anoncreds-wasm/anoncreds_wasm_bg.wasm'; -import * as Types from "./types"; -import { isNil } from "../../../utils"; +import * as Types from "../types"; +import { isNil } from "../../../../utils"; /** * @class AnoncredsLoader @@ -23,7 +23,6 @@ export class AnoncredsLoader { private async load() { try { - this.pkg ??= await import("anoncreds-wasm").then(async module => { const wasmInstance = module.initSync({ module: wasmBuffer }); await module.default(wasmInstance); diff --git a/src/pollux/plugins/anoncreds/types.ts b/src/plugins/internal/anoncreds/types.ts similarity index 100% rename from src/pollux/plugins/anoncreds/types.ts rename to src/plugins/internal/anoncreds/types.ts diff --git a/src/pollux/plugins/dif/IsCredentialRevoked.ts b/src/plugins/internal/dif/IsCredentialRevoked.ts similarity index 91% rename from src/pollux/plugins/dif/IsCredentialRevoked.ts rename to src/plugins/internal/dif/IsCredentialRevoked.ts index d2a4abc43..44de36ad5 100644 --- a/src/pollux/plugins/dif/IsCredentialRevoked.ts +++ b/src/plugins/internal/dif/IsCredentialRevoked.ts @@ -4,12 +4,15 @@ import * as jsonld from 'jsonld'; import { JsonLd, RemoteDocument } from "jsonld/jsonld-spec"; import * as Domain from "../../../domain"; import { revocationJsonldDocuments } from "../../../domain/models/revocation"; -import { Task, isObject } from "../../../utils"; -import { JWTCredential } from "../../models/JWTVerifiableCredential"; -import { Bitstring } from "../../utils/Bitstring"; +import { isObject } from "../../../utils"; +import { JWTCredential } from "../../../pollux/models/JWTVerifiableCredential"; +import { Bitstring } from "../../../pollux/utils/Bitstring"; +import type { Context } from "./index"; // TODO dont import from Castor, lift to domain? import { VerificationKeyType } from "../../../castor/types"; +import { Plugins } from '../../types'; +import { Payload } from '../../../domain/protocols/Payload'; export enum JWTRevocationStatusPurpose { @@ -62,8 +65,8 @@ interface Args { credential: JWTCredential; } -export class IsCredentialRevoked extends Task { - async run(ctx: Task.Context) { +export class IsCredentialRevoked extends Plugins.Task { + async run(ctx: Context) { const credential = this.args.credential; if (credential instanceof JWTCredential) { @@ -74,7 +77,7 @@ export class IsCredentialRevoked extends Task { ); } // Credential is non revocable - return false; + return Payload.make("isCredentialRevoked", false); } const revocationStatus = credential.credentialStatus; @@ -85,7 +88,8 @@ export class IsCredentialRevoked extends Task { `CredentialStatus response revocation type not supported` ); } - return this.verifyRevocationProof(ctx, response, revocationStatus.statusListIndex); + const result = await this.verifyRevocationProof(ctx, response, revocationStatus.statusListIndex); + return Payload.make("isCredentialRevoked", result); } throw new Domain.PolluxError.CredentialRevocationTypeInvalid("Only JWT Credential are supported"); @@ -95,7 +99,7 @@ export class IsCredentialRevoked extends Task { return isObject(credentialStatus) && credentialStatus.type === 'StatusList2021Entry'; } - private async fetchRevocationRegistry(ctx: Task.Context, revocationStatus: JWTRevocationStatus) { + private async fetchRevocationRegistry(ctx: Context, revocationStatus: JWTRevocationStatus) { const response = await ctx.Api.request("GET", revocationStatus.statusListCredential); if (response.httpStatus !== 200) { @@ -111,7 +115,7 @@ export class IsCredentialRevoked extends Task { } private async verifyRevocationProof( - ctx: Task.Context, + ctx: Context, revocation: JWTStatusListResponse, statusListIndex: number ): Promise { @@ -188,7 +192,7 @@ export class IsCredentialRevoked extends Task { } } - private async encode(ctx: Task.Context, data: any) { + private async encode(ctx: Context, data: any) { const customLoader = async (url: string) => { const cached = revocationJsonldDocuments[url as keyof typeof revocationJsonldDocuments]; if (cached) { diff --git a/src/pollux/plugins/dif/PresentationRequest.ts b/src/plugins/internal/dif/PresentationRequest.ts similarity index 78% rename from src/pollux/plugins/dif/PresentationRequest.ts rename to src/plugins/internal/dif/PresentationRequest.ts index 4452e8fc5..97ec95fae 100644 --- a/src/pollux/plugins/dif/PresentationRequest.ts +++ b/src/plugins/internal/dif/PresentationRequest.ts @@ -1,10 +1,11 @@ import { uuid } from "@stablelib/uuid"; import * as Domain from "../../../domain"; -import { Pollux } from "../../PlugPol"; -import { DIF } from "./types"; -import { JWTCredential } from "../../models/JWTVerifiableCredential"; -import { SDJWTCredential } from "../../models/SDJWTVerifiableCredential"; +import { JWTCredential } from "../../../pollux/models/JWTVerifiableCredential"; +import { SDJWTCredential } from "../../../pollux/models/SDJWTVerifiableCredential"; import { Payload } from "../../../domain/protocols/Payload"; +import { DIF } from "./types"; +import type { Context } from "./index"; +import { Plugins } from "../../../plugins"; // make a Presentation from a PresentationRequest @@ -13,28 +14,13 @@ interface Args { presentationRequest: DIF.Presentation.Request; } -export class PresentationRequest extends Pollux.Task { - async run(ctx: Pollux.Context) { - // private async createJWTPresentationSubmission( - // presentationDefinitionRequest: any, - // credential: Credential, - // privateKey: PrivateKey, - // options?: { - // presentationFrame?: PresentationFrame, - // domain?: string, - // challenge?: string - // } - // ): Promise> { - +export class PresentationRequest extends Plugins.Task { + async run(ctx: Context) { const credential = this.args.credential; const presentationRequest = this.args.presentationRequest; const presentationDefinition = presentationRequest.presentation_definition; const inputDescriptors = presentationDefinition.input_descriptors ?? []; - // if (!(credential instanceof JWTCredential)) { - // throw new Domain.PolluxError.InvalidCredentialError("Expected JWT Credential"); - // } - if (!credential.isProvable()) { throw new Domain.PolluxError.InvalidCredentialError("Cannot create proofs with this type of credential."); } @@ -42,8 +28,6 @@ export class PresentationRequest extends Pollux.Task { const jws = await this.getJWS(ctx); const descriptorMap = inputDescriptors.map((inputDescriptor) => { - // TODO map dependent on CredentialType - if (credential instanceof SDJWTCredential) { return { format: "sdjwt" as any, @@ -83,7 +67,7 @@ export class PresentationRequest extends Pollux.Task { return Payload.make(DIF.PRESENTATION, presentation); } - private async getJWS(ctx: Pollux.Context) { + private async getJWS(ctx: Context) { const subject = Domain.DID.from(this.args.credential.subject); const privateKeys = await ctx.Pluto.getDIDPrivateKeysByDID(subject); const privateKey = privateKeys.at(0); diff --git a/src/pollux/plugins/dif/PresentationVerify.ts b/src/plugins/internal/dif/PresentationVerify.ts similarity index 91% rename from src/pollux/plugins/dif/PresentationVerify.ts rename to src/plugins/internal/dif/PresentationVerify.ts index 5cb155a01..a8f24a7c6 100644 --- a/src/pollux/plugins/dif/PresentationVerify.ts +++ b/src/plugins/internal/dif/PresentationVerify.ts @@ -1,34 +1,34 @@ import * as Domain from "../../../domain"; -import { JWTCredential } from "../../models/JWTVerifiableCredential"; import { asArray, asJsonObj, expect } from "../../../utils"; -import { DescriptorPath } from "../../utils/DescriptorPath"; -import { Pollux } from "../../PlugPol"; import { IsCredentialRevoked } from "./IsCredentialRevoked"; -import { DIF } from "./types"; -import { SDJWTCredential } from "../../models/SDJWTVerifiableCredential"; import { Payload } from "../../../domain/protocols/Payload"; +import { JWTCredential } from "../../../pollux/models/JWTVerifiableCredential"; +import { SDJWTCredential } from "../../../pollux/models/SDJWTVerifiableCredential"; +import { DescriptorPath } from "../../../pollux/utils/DescriptorPath"; +import { DIF } from "./types"; +import type { Context } from "./index"; +import { Plugins } from "../../../plugins"; interface Args { presentation: DIF.EmbedTarget; presentationRequest: DIF.Presentation.Request; } -export class PresentationVerify extends Pollux.Task { - async run(ctx: Pollux.Context) { +export class PresentationVerify extends Plugins.Task { + async run(ctx: Context) { const presentation = this.args.presentation; const presentationRequest = this.args.presentationRequest; - const valid = ( - this.isValidSubmission(presentation) - && this.isValidPresentationRequest(presentationRequest) - ) - ? this.verifyPresentationSubmissionJWT(ctx, presentation, presentationRequest) + const validPresentation = this.isValidPresentation(presentation); + const validRequest = this.isValidPresentationRequest(presentationRequest); + const valid = validPresentation && validRequest + ? await this.verify(ctx, presentation, presentationRequest) : false; return Payload.make("valid", valid); } - private isValidSubmission(data: any): data is DIF.EmbedTarget { + private isValidPresentation(data: any): data is DIF.EmbedTarget { if (!data || (data && typeof data !== "object")) { return false; } @@ -51,8 +51,8 @@ export class PresentationVerify extends Pollux.Task { return typeof data === "object" ? true : false; } - private async verifyPresentationSubmissionJWT( - ctx: Pollux.Context, + private async verify( + ctx: Context, presentationSubmission: DIF.EmbedTarget, presentationRequest: DIF.Presentation.Request ): Promise { @@ -69,7 +69,7 @@ export class PresentationVerify extends Pollux.Task { ); const presentation = SDJWTCredential.fromJWS(jws); - const issuer = presentation.issuer; + // const issuer = presentation.issuer; if ("challenge" in presentationRequest && "domain" in presentationRequest) { const challenge = presentationRequest?.challenge; @@ -146,8 +146,6 @@ export class PresentationVerify extends Pollux.Task { throw new Domain.PolluxError.InvalidVerifyCredentialError(jws, "Invalid Holder Presentation JWS Signature"); } - let verifiableCredentialPropsMapper: DescriptorPath; - // if (descriptorItem.format !== OEA.JWT_VP) { // throw new Error(""); // } @@ -171,7 +169,7 @@ export class PresentationVerify extends Pollux.Task { const revocationTask = new IsCredentialRevoked({ credential: verifiableCredential }); const isRevoked = await ctx.run(revocationTask); - if (isRevoked) { + if (isRevoked.data) { throw new Domain.PolluxError.InvalidVerifyCredentialError(vc, "Invalid Verifiable Presentation, credential is revoked"); } } catch (err) { @@ -190,9 +188,7 @@ export class PresentationVerify extends Pollux.Task { if (!verifiableCredentialValid) { throw new Domain.PolluxError.InvalidVerifyCredentialError(vc, "Invalid Presentation Credential JWS Signature"); } - verifiableCredentialPropsMapper = new DescriptorPath(verifiableCredential); - - + const verifiableCredentialPropsMapper = new DescriptorPath(verifiableCredential); const inputDescriptor = inputDescriptors.find((inputDescriptor) => inputDescriptor.id === descriptorItem.id); this.validateInputDescriptor( @@ -243,7 +239,6 @@ export class PresentationVerify extends Pollux.Task { if (pattern.test(fieldInVC) || fieldInVC === filter.pattern) { validClaim = true; } else { - const t = "testing"; reason = `Expected the ${path} field to be "${filter.pattern}" but got "${fieldInVC}"`; } } else if (filter.enum) { diff --git a/src/plugins/internal/dif/index.ts b/src/plugins/internal/dif/index.ts new file mode 100644 index 000000000..d3c4de01c --- /dev/null +++ b/src/plugins/internal/dif/index.ts @@ -0,0 +1,20 @@ +import { Plugin } from "../../Plugin"; +import { Plugins } from "../../types"; +import { IsCredentialRevoked } from "./IsCredentialRevoked"; +import { PresentationRequest } from "./PresentationRequest"; +import { PresentationVerify } from "./PresentationVerify"; +import { DIFModule } from "./module"; +import { DIF } from "./types"; +import type { Context as ACContext } from "../anoncreds"; + +export type Modules = { DIF: DIFModule; }; +export type Context = Plugins.Context; + +const plugin = new Plugin() + .addModule("DIF", new DIFModule()) + .register(DIF.PRESENTATION_REQUEST, PresentationRequest) + .register(DIF.PRESENTATION, PresentationVerify) + // ??? tmp workaround Revocation being extracted to separate handlers + .register("revocation-check/prism/jwt", IsCredentialRevoked); + +export default plugin; diff --git a/src/pollux/plugins/dif/CreatePresentationDefinition.ts b/src/plugins/internal/dif/module/CreatePresentationDefinition.ts similarity index 55% rename from src/pollux/plugins/dif/CreatePresentationDefinition.ts rename to src/plugins/internal/dif/module/CreatePresentationDefinition.ts index 1eb891cb7..1a7e10879 100644 --- a/src/pollux/plugins/dif/CreatePresentationDefinition.ts +++ b/src/plugins/internal/dif/module/CreatePresentationDefinition.ts @@ -1,9 +1,9 @@ import { uuid } from "@stablelib/uuid"; -import * as Domain from "../../../domain"; -import { Pollux } from "../../PlugPol"; -import { DIF } from "./types"; -import { JsonObj } from "../../../utils"; -import { Payload } from "../../../domain/protocols/Payload"; +import * as Domain from "../../../../domain"; +import { DIF } from "../types"; +import { JsonObj } from "../../../../utils"; +import { Payload } from "../../../../domain/protocols/Payload"; +import { Plugins } from "../../../../plugins"; interface Args { claims: JsonObj; @@ -11,8 +11,8 @@ interface Args { // TODO not used - schema: string; } -export class CreatePresentationDefinition extends Pollux.Task { - async run(ctx: Pollux.Context) { +export class CreatePresentationDefinition extends Plugins.Task { + async run() { const contraintFields = Object.keys(this.args.claims) .map((path) => ({ path: [ @@ -72,44 +72,4 @@ export class CreatePresentationDefinition extends Pollux.Task { return Payload.make("presentation-definition", request); } - - // TODO check originals logic actually works - validatePresentationClaims(claims: any) { - - if (claims.schema && typeof claims.schema !== 'string') { - return false; - } - if (claims.issuer && typeof claims.issuer !== 'string') { - return false; - } - if (!claims.claims) { - return false; - } - - // TODO does this actually validate? return values are swallowed! - Object.keys(claims.claims).forEach((field) => { - const filter: any = claims.claims[field]; - - if (!filter.type || typeof filter.type !== 'string') { - return false; - } - if (filter.pattern && typeof filter.pattern !== 'string') { - return false; - } - if (filter.pattern && typeof filter.pattern !== 'string') { - return false; - } - if (filter.enum && Array.isArray(filter.enum)) { - return false; - } - if (filter.const && Array.isArray(filter.const)) { - return false; - } - if (filter.value && typeof filter.value !== 'string' && typeof filter.value !== 'number') { - return false; - } - }); - - return true; - } } diff --git a/src/plugins/internal/dif/module/index.ts b/src/plugins/internal/dif/module/index.ts new file mode 100644 index 000000000..6722874d9 --- /dev/null +++ b/src/plugins/internal/dif/module/index.ts @@ -0,0 +1,20 @@ +import { JsonObj, Task, asJsonObj } from "../../../../utils"; +import { CreatePresentationDefinition } from "./CreatePresentationDefinition"; +import { DIF } from "../types"; + +export class DIFModule extends Task.Runner { + clone() { + return new DIFModule(); + } + + async createPresentationDefinition( + claims: JsonObj, + opts?: { + issuer?: string; + } + ) { + const task = new CreatePresentationDefinition({ claims, ...asJsonObj(opts) }); + const result = await this.runTask(task); + return result.data; + } +} diff --git a/src/pollux/plugins/dif/types.ts b/src/plugins/internal/dif/types.ts similarity index 99% rename from src/pollux/plugins/dif/types.ts rename to src/plugins/internal/dif/types.ts index 4f2894709..5ec1d7d7f 100644 --- a/src/pollux/plugins/dif/types.ts +++ b/src/plugins/internal/dif/types.ts @@ -1,5 +1,4 @@ export namespace DIF { - // export const PRESENTATION_REQUEST = 'dif/presentation-exchange/definitions@v1.0'; export const PRESENTATION = 'dif/presentation-exchange/submission@v1.0'; diff --git a/src/pollux/plugins/oea/Plugin.ts b/src/plugins/internal/oea/index.ts similarity index 74% rename from src/pollux/plugins/oea/Plugin.ts rename to src/plugins/internal/oea/index.ts index 65c2abfa4..e484a3ae8 100644 --- a/src/pollux/plugins/oea/Plugin.ts +++ b/src/plugins/internal/oea/index.ts @@ -3,20 +3,16 @@ import { OEA } from "./types"; import * as jwt from "./jwt"; import * as sdjwt from "./sdjwt"; -// Register protocol handlers -// or export registerable handler plugin const plugin = new Plugin(); // jwt handlers plugin.register(`credential-issue/${OEA.PRISM_JWT}`, jwt.CredentialIssue); plugin.register(`credential-offer/${OEA.PRISM_JWT}`, jwt.CredentialOffer); plugin.register(`presentation-request/${OEA.PRISM_JWT}`, jwt.PresentationRequest); -plugin.register(`presentation-submission/${OEA.PRISM_JWT}`, jwt.PresentationVerify); // sdjwt handlers plugin.register(`credential-issue/${OEA.PRISM_SDJWT}`, sdjwt.CredentialIssue); plugin.register(`credential-offer/${OEA.PRISM_SDJWT}`, sdjwt.CredentialOffer); plugin.register(`presentation-request/${OEA.PRISM_SDJWT}`, sdjwt.PresentationRequest); -plugin.register(`presentation-submission/${OEA.PRISM_SDJWT}`, sdjwt.PresentationVerify); export default plugin; diff --git a/src/pollux/plugins/oea/jwt/CredentialIssue.ts b/src/plugins/internal/oea/jwt/CredentialIssue.ts similarity index 76% rename from src/pollux/plugins/oea/jwt/CredentialIssue.ts rename to src/plugins/internal/oea/jwt/CredentialIssue.ts index fe44bdea8..20d7e8d61 100644 --- a/src/pollux/plugins/oea/jwt/CredentialIssue.ts +++ b/src/plugins/internal/oea/jwt/CredentialIssue.ts @@ -1,12 +1,12 @@ +import { Plugins } from "../../../../plugins"; import { Payload } from "../../../../domain/protocols/Payload"; -import { JWTCredential } from "../../../models/JWTVerifiableCredential"; -import { Pollux } from "../../../PlugPol"; +import { JWTCredential } from "../../../../pollux/models/JWTVerifiableCredential"; interface Args { data: any; } -export class CredentialIssue extends Pollux.Task { +export class CredentialIssue extends Plugins.Task { async run() { const jws = this.getJWS(); // TODO use context.JWT diff --git a/src/pollux/plugins/oea/jwt/CredentialOffer.ts b/src/plugins/internal/oea/jwt/CredentialOffer.ts similarity index 90% rename from src/pollux/plugins/oea/jwt/CredentialOffer.ts rename to src/plugins/internal/oea/jwt/CredentialOffer.ts index 59951e935..6e3d7401d 100644 --- a/src/pollux/plugins/oea/jwt/CredentialOffer.ts +++ b/src/plugins/internal/oea/jwt/CredentialOffer.ts @@ -1,16 +1,15 @@ import * as Domain from "../../../../domain"; import { Payload } from "../../../../domain/protocols/Payload"; -import { Pollux } from "../../../PlugPol"; import { PrismKeyPathIndexTask } from "../../../../edge-agent/didFunctions"; -import { Task } from "../../../../utils"; import { OEA } from "../types"; +import { Plugins } from "../../../../plugins"; interface Args { offer: OEA.CredentialOffer; } -export class CredentialOffer extends Pollux.Task { - async run(ctx: Task.Context) { +export class CredentialOffer extends Plugins.Task { + async run(ctx: Plugins.Context) { const offer = this.args.offer; // TODO move to CreatePrismDID task diff --git a/src/pollux/plugins/oea/jwt/PresentationRequest.ts b/src/plugins/internal/oea/jwt/PresentationRequest.ts similarity index 68% rename from src/pollux/plugins/oea/jwt/PresentationRequest.ts rename to src/plugins/internal/oea/jwt/PresentationRequest.ts index 7bcabdae5..487a88741 100644 --- a/src/pollux/plugins/oea/jwt/PresentationRequest.ts +++ b/src/plugins/internal/oea/jwt/PresentationRequest.ts @@ -1,20 +1,18 @@ import * as Domain from "../../../../domain"; -import { JWTCredential } from "../../../models/JWTVerifiableCredential"; -import { Task } from "../../../../utils"; -// import { CreateJWT } from "../../../utils/jwt/CreateJwt"; -import { Pollux } from "../../../PlugPol"; -import { OEA } from "../types"; +import { JWTCredential } from "../../../../pollux/models/JWTVerifiableCredential"; import { Payload } from "../../../../domain/protocols/Payload"; +import { OEA } from "../types"; +import { Plugins } from "../../../../plugins"; interface Args { credential: Domain.Credential; presentationRequest: OEA.PresentationRequest; } -export class PresentationRequest extends Pollux.Task { - async run(ctx: Task.Context) { +export class PresentationRequest extends Plugins.Task { + async run(ctx: Plugins.Context) { const credential = this.args.credential; - const presReq = this.args.presentationRequest; + const presentationRequest = this.args.presentationRequest; if (!(credential instanceof JWTCredential)) { throw new Error(); @@ -24,8 +22,6 @@ export class PresentationRequest extends Pollux.Task { throw new Error("Credential is not Provable"); } - // ?? this is all cloud agent specific logic - const subjectDID = Domain.DID.from(credential.subject); // TODO these values should be passed // const keys = await ctx.Pluto.getDIDPrivateKeysByDID(subjectDID); @@ -33,20 +29,16 @@ export class PresentationRequest extends Pollux.Task { // ? Domain.Curve.ED25519 // : Domain.Curve.SECP256K1; // const privateKey = keys.find((key) => key.curve === curve); - // if (privateKey === undefined) { // throw new Domain.AgentError.CannotFindDIDPrivateKey(); // } const presentation = credential.presentation(); - // ? something like this - make a desired presentation - // const presentation = ctx.Pollux.makePresentation("https://www.w3.org/2018/credentials/v1", { ...args }); - const payload = { vp: presentation, iss: subjectDID.toString(), - aud: presReq.options.domain, - nonce: presReq.options.challenge, + aud: presentationRequest.options.domain, + nonce: presentationRequest.options.challenge, }; // const signedJWT = await Domain.JWT.sign(did, privateKey, payload, { kid }); diff --git a/src/pollux/plugins/oea/sdjwt/index.ts b/src/plugins/internal/oea/jwt/index.ts similarity index 74% rename from src/pollux/plugins/oea/sdjwt/index.ts rename to src/plugins/internal/oea/jwt/index.ts index 254c48c28..95c250f7b 100644 --- a/src/pollux/plugins/oea/sdjwt/index.ts +++ b/src/plugins/internal/oea/jwt/index.ts @@ -1,4 +1,3 @@ export * from "./CredentialIssue"; export * from "./CredentialOffer"; export * from "./PresentationRequest"; -export * from "./PresentationVerify"; diff --git a/src/pollux/plugins/oea/sdjwt/CredentialIssue.ts b/src/plugins/internal/oea/sdjwt/CredentialIssue.ts similarity index 76% rename from src/pollux/plugins/oea/sdjwt/CredentialIssue.ts rename to src/plugins/internal/oea/sdjwt/CredentialIssue.ts index 724dd9e1e..9b654145e 100644 --- a/src/pollux/plugins/oea/sdjwt/CredentialIssue.ts +++ b/src/plugins/internal/oea/sdjwt/CredentialIssue.ts @@ -1,12 +1,12 @@ import { Payload } from "../../../../domain/protocols/Payload"; -import { SDJWTCredential } from "../../../models/SDJWTVerifiableCredential"; -import { Pollux } from "../../../PlugPol"; +import { SDJWTCredential } from "../../../../pollux/models/SDJWTVerifiableCredential"; +import { Plugins } from "../../../../plugins"; interface Args { data: any; } -export class CredentialIssue extends Pollux.Task { +export class CredentialIssue extends Plugins.Task { async run() { const jws = this.getJWS(); // TODO use context.SDJWT diff --git a/src/pollux/plugins/oea/sdjwt/CredentialOffer.ts b/src/plugins/internal/oea/sdjwt/CredentialOffer.ts similarity index 86% rename from src/pollux/plugins/oea/sdjwt/CredentialOffer.ts rename to src/plugins/internal/oea/sdjwt/CredentialOffer.ts index 5d22e02e9..9ca303cb8 100644 --- a/src/pollux/plugins/oea/sdjwt/CredentialOffer.ts +++ b/src/plugins/internal/oea/sdjwt/CredentialOffer.ts @@ -1,16 +1,15 @@ import * as Domain from "../../../../domain"; -import { Pollux } from "../../../PlugPol"; import { PrismKeyPathIndexTask } from "../../../../edge-agent/didFunctions"; import { Payload } from "../../../../domain/protocols/Payload"; -import { Task } from "../../../../utils"; import { OEA } from "../types"; +import { Plugins } from "../../../../plugins"; interface Args { offer: OEA.CredentialOffer; } -export class CredentialOffer extends Pollux.Task { - async run(ctx: Task.Context) { +export class CredentialOffer extends Plugins.Task { + async run(ctx: Plugins.Context) { const offer = this.args.offer; // TODO validate @@ -51,7 +50,7 @@ export class CredentialOffer extends Pollux.Task { }, }; - const signedJWT = await ctx.JWT.signWithDID(did, payload); + const signedJWT = await ctx.JWT.signWithDID(did, payload, undefined, authSk); return Payload.make(OEA.PRISM_SDJWT, signedJWT); } diff --git a/src/pollux/plugins/oea/sdjwt/PresentationRequest.ts b/src/plugins/internal/oea/sdjwt/PresentationRequest.ts similarity index 75% rename from src/pollux/plugins/oea/sdjwt/PresentationRequest.ts rename to src/plugins/internal/oea/sdjwt/PresentationRequest.ts index a4112599e..ac793f39b 100644 --- a/src/pollux/plugins/oea/sdjwt/PresentationRequest.ts +++ b/src/plugins/internal/oea/sdjwt/PresentationRequest.ts @@ -1,20 +1,20 @@ import * as Domain from "../../../../domain"; import { Payload } from "../../../../domain/protocols/Payload"; -import { SDJWTCredential } from "../../../models/SDJWTVerifiableCredential"; -import { Pollux } from "../../../PlugPol"; +import { SDJWTCredential } from "../../../../pollux/models/SDJWTVerifiableCredential"; import { OEA } from "../types"; +import { Plugins } from "../../../../plugins"; interface Args { credential: Domain.Credential; - privateKey: Domain.PrivateKey; presentationRequest: OEA.PresentationRequest; presentationFrame?: any; } -export class PresentationRequest extends Pollux.Task { - async run(ctx: Pollux.Context) { +export class PresentationRequest extends Plugins.Task { + async run(ctx: Plugins.Context) { const credential = this.args.credential; - const presentationRequest = this.args.presentationRequest; + // TODO is this not used? + // const presentationRequest = this.args.presentationRequest; if (credential instanceof SDJWTCredential) { const subjectDID = Domain.DID.from(credential.subject); @@ -30,7 +30,7 @@ export class PresentationRequest extends Pollux.Task { const presentationJWS = await ctx.SDJWT.createPresentationFor({ jws: credential.id, presentationFrame, - privateKey: this.args.privateKey + privateKey, }); return Payload.make(OEA.PRISM_SDJWT, presentationJWS); diff --git a/src/pollux/plugins/oea/jwt/index.ts b/src/plugins/internal/oea/sdjwt/index.ts similarity index 74% rename from src/pollux/plugins/oea/jwt/index.ts rename to src/plugins/internal/oea/sdjwt/index.ts index 254c48c28..95c250f7b 100644 --- a/src/pollux/plugins/oea/jwt/index.ts +++ b/src/plugins/internal/oea/sdjwt/index.ts @@ -1,4 +1,3 @@ export * from "./CredentialIssue"; export * from "./CredentialOffer"; export * from "./PresentationRequest"; -export * from "./PresentationVerify"; diff --git a/src/pollux/plugins/oea/types.ts b/src/plugins/internal/oea/types.ts similarity index 100% rename from src/pollux/plugins/oea/types.ts rename to src/plugins/internal/oea/types.ts diff --git a/src/plugins/types.ts b/src/plugins/types.ts new file mode 100644 index 000000000..0a2ccbfc1 --- /dev/null +++ b/src/plugins/types.ts @@ -0,0 +1,14 @@ +import * as Utils from "../utils/tasks"; +import { Payload } from "../domain/protocols/Payload"; +import { JWT, SDJWT } from "../pollux/utils/jwt"; + +export namespace Plugins { + export abstract class Task extends Utils.Task {} + + export interface InternalModules { + JWT: JWT; + SDJWT: SDJWT; + } + + export type Context> = Utils.Task.Context; +} diff --git a/src/pollux/PlugPol.ts b/src/pollux/PlugPol.ts deleted file mode 100644 index 2a8f60409..000000000 --- a/src/pollux/PlugPol.ts +++ /dev/null @@ -1,57 +0,0 @@ -import * as Domain from "../domain"; -import { JsonObj, notNil } from "../utils"; -import * as Utils from "../utils/tasks"; -import { Payload } from "../domain/protocols/Payload"; -import { Plugin } from "../plugins"; - -export namespace Pollux { - export abstract class Task extends Utils.Task {} - - export type Context = Utils.Task.Context; -} - -// ? should be renamed to PluginManager / ModuleManager -export class PlugPol implements Domain.Pollux { - private readonly plugins: Plugin[] = []; - - constructor( - private readonly dependencies: Utils.Task.Context.Base - ) {} - - async start() {} - - register(plugin: Plugin) { - this.plugins.push(plugin); - } - - async handle( - protocolType: string, - protocolId: string, - payload: JsonObj, - ): Promise { - const protocolCtor = this.findProtocol(protocolType, protocolId); - // ? move to re-using current context... - const ctx = Utils.Task.Context.make(this.dependencies); - const extensions = this.plugins - .map(x => x.extensions) - .reduce((acc, x) => Object.assign(acc, x), {}); - - ctx.extend(extensions); - - const task = new protocolCtor(payload); - const result = await ctx.run(task); - return result; - } - - private findProtocol(type: string, id: string) { - for (const plugin of this.plugins) { - const protocol = plugin.tasks.get(id) ?? plugin.tasks.get(`${type}/${id}`); - - if (notNil(protocol)) { - return protocol; - } - } - - throw new Error(`Protocol handler not found for ${id} (${type})`); - } -} diff --git a/src/pollux/index.ts b/src/pollux/index.ts deleted file mode 100644 index 84f13b052..000000000 --- a/src/pollux/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -import { PlugPol } from "./PlugPol"; -export default PlugPol; diff --git a/src/pollux/models/SDJWTVerifiableCredential.ts b/src/pollux/models/SDJWTVerifiableCredential.ts index 5d14f5fb3..7cd455ac9 100644 --- a/src/pollux/models/SDJWTVerifiableCredential.ts +++ b/src/pollux/models/SDJWTVerifiableCredential.ts @@ -41,7 +41,8 @@ export class SDJWTCredential extends Credential implements ProvableCredential, S } get subject() { - return this.claims.at(0)?.sub ?? this.properties.get(JWT.Claims.sub); + const sub = this.claims.at(0)?.sub ?? this.properties.get(JWT.Claims.sub); + return sub instanceof Disclosure ? sub.value : sub; } public uuid: string = uuid(); diff --git a/src/pollux/plugins/anoncreds/Plugin.ts b/src/pollux/plugins/anoncreds/Plugin.ts deleted file mode 100644 index b2f02128e..000000000 --- a/src/pollux/plugins/anoncreds/Plugin.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Plugin } from "../../../plugins"; -import * as Types from "./types"; - -import { AnoncredsLoader } from "./AnoncredsLoader"; -import { CredentialIssue } from "./CredentialIssue"; -import { CredentialOffer } from "./CredentialOffer"; -import { PresentationRequest } from "./PresentationRequest"; -import { PresentationVerify } from "./PresentationVerify"; - -type MyPlugin = Plugin.ExtractExtension; - -declare module "../../../utils/tasks" { - interface Extension extends MyPlugin {} -} - -const plugin = new Plugin() - .extend("Anoncreds", new AnoncredsLoader()); - -plugin.register(Types.CREDENTIAL_ISSUE, CredentialIssue); -plugin.register(Types.CREDENTIAL_OFFER, CredentialOffer); -plugin.register(Types.PRESENTATION, PresentationVerify); -plugin.register(Types.PRESENTATION_REQUEST, PresentationRequest); - -export default plugin; diff --git a/src/pollux/plugins/dif/Plugin.ts b/src/pollux/plugins/dif/Plugin.ts deleted file mode 100644 index d2e19267c..000000000 --- a/src/pollux/plugins/dif/Plugin.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Plugin } from "../../../plugins"; -import { JsonObj, Task, asJsonObj } from "../../../utils"; -import { CreatePresentationDefinition } from "./CreatePresentationDefinition"; -import { IsCredentialRevoked } from "./IsCredentialRevoked"; -import { PresentationRequest } from "./PresentationRequest"; -import { PresentationVerify } from "./PresentationVerify"; -import { DIF } from "./types"; - -class DIFModule extends Task.Runner { - clone() { - return new DIFModule(); - } - - async createPresentationDefinition( - claims: JsonObj, - opts?: { - issuer?: string; - } - ) { - const task = new CreatePresentationDefinition({ claims, ...asJsonObj(opts) }); - const result = await this.runTask(task); - return result.data; - } -} - -const plugin = new Plugin() - .extend("DIF", new DIFModule()); - -plugin.register(DIF.PRESENTATION_REQUEST, PresentationRequest); -plugin.register(DIF.PRESENTATION, PresentationVerify); - -// ??? tmp workaround Revocation being extracted to separate handlers -plugin.register("revocation-check/prism/jwt", IsCredentialRevoked); - -export default plugin; diff --git a/src/pollux/plugins/dif/index.ts b/src/pollux/plugins/dif/index.ts deleted file mode 100644 index 3721d73d6..000000000 --- a/src/pollux/plugins/dif/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Plugin } from "../../../plugins"; -import module from "./Plugin"; - -/** - * Extract any Context extensions from Plugin - */ -type IModule = Plugin.ExtractExtension; - -/** - * Declaration Merge to make TS aware - */ -declare module "../../../utils/tasks" { - interface Extension extends IModule {} -} - -export default module; diff --git a/src/pollux/plugins/oea/index.ts b/src/pollux/plugins/oea/index.ts deleted file mode 100644 index 3721d73d6..000000000 --- a/src/pollux/plugins/oea/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Plugin } from "../../../plugins"; -import module from "./Plugin"; - -/** - * Extract any Context extensions from Plugin - */ -type IModule = Plugin.ExtractExtension; - -/** - * Declaration Merge to make TS aware - */ -declare module "../../../utils/tasks" { - interface Extension extends IModule {} -} - -export default module; diff --git a/src/pollux/plugins/oea/jwt/PresentationVerify.ts b/src/pollux/plugins/oea/jwt/PresentationVerify.ts deleted file mode 100644 index e27f467fa..000000000 --- a/src/pollux/plugins/oea/jwt/PresentationVerify.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Pollux } from "../../../PlugPol"; -import { OEA } from "../../oea/types"; -import { Payload } from "../../../../domain/protocols/Payload"; - -interface Args { - presentation: OEA.PresentationSubmission; - presentationRequest: OEA.PresentationRequest; -} - -export class PresentationVerify extends Pollux.Task { - async run() { - const presentation = this.args.presentation; - const presentationRequest = this.args.presentationRequest; - const valid = false; - return Payload.make("valid", valid); - } -} diff --git a/src/pollux/plugins/oea/sdjwt/PresentationVerify.ts b/src/pollux/plugins/oea/sdjwt/PresentationVerify.ts deleted file mode 100644 index d612db0ef..000000000 --- a/src/pollux/plugins/oea/sdjwt/PresentationVerify.ts +++ /dev/null @@ -1,20 +0,0 @@ -import * as Domain from "../../../../domain"; -import { asJsonObj } from "../../../../utils"; -import { DescriptorPath } from "../../../utils/DescriptorPath"; -import { Pollux } from "../../../PlugPol"; -import { OEA } from "../../oea/types"; -import { Payload } from "../../../../domain/protocols/Payload"; - -interface Args { - presentation: OEA.PresentationSubmission; - presentationRequest: OEA.PresentationRequest; -} - -export class PresentationVerify extends Pollux.Task { - async run() { - const presentation = this.args.presentation; - const presentationRequest = this.args.presentationRequest; - const valid = false; - return Payload.make("valid", valid); - } -} diff --git a/src/pollux/utils/jwt/JWT.ts b/src/pollux/utils/jwt/JWT.ts index 477f73721..7eccf88ca 100644 --- a/src/pollux/utils/jwt/JWT.ts +++ b/src/pollux/utils/jwt/JWT.ts @@ -27,9 +27,10 @@ export class JWT extends Task.Runner { signWithDID( did: Domain.DID, payload: Partial, - header?: Partial + header?: Partial, + privateKey?: Domain.PrivateKey ): Promise { - return this.runTask(new CreateJWT({ did, payload, header })); + return this.runTask(new CreateJWT({ did, payload, header, privateKey })); } async verify(options: { @@ -56,7 +57,6 @@ export class JWT extends Task.Runner { } const decoded = await this.decode(jws); - let verified = true; for (const vm of verificationMethods) { try { @@ -69,16 +69,16 @@ export class JWT extends Task.Runner { const decodedSignature = base64url.baseDecode(decoded.signature); const passed = pk.verify(Buffer.from(decoded.data), Buffer.from(decodedSignature)); - if (!passed) { - throw new Error("failed"); + if (passed === true) { + return passed; } } catch { - verified = false; + // return false; } } - return verified; + return false; } catch { return false; } diff --git a/src/pollux/utils/jwt/PKInstance.ts b/src/pollux/utils/jwt/PKInstance.ts index 1f303f96c..bbf33542c 100644 --- a/src/pollux/utils/jwt/PKInstance.ts +++ b/src/pollux/utils/jwt/PKInstance.ts @@ -2,7 +2,7 @@ import type * as DIDResolver from "did-resolver"; import { base58btc } from 'multiformats/bases/base58'; import { base64url } from "multiformats/bases/base64"; import * as Domain from "../../../domain"; -import { Task } from "../../../utils"; +import { Task, expect } from "../../../utils"; // TODO importing from Castor import { VerificationKeyType } from "../../../castor/types"; @@ -36,18 +36,20 @@ export class PKInstance extends Task { } if (verificationMethod.publicKeyJwk) { - const { crv, x } = verificationMethod.publicKeyJwk; + const crv = expect(verificationMethod.publicKeyJwk.crv); + const x = expect(verificationMethod.publicKeyJwk.x); + if (crv === Domain.Curve.ED25519) { pk = ctx.Apollo.createPublicKey({ [Domain.KeyProperties.curve]: Domain.Curve.ED25519, [Domain.KeyProperties.type]: Domain.KeyTypes.EC, - [Domain.KeyProperties.rawKey]: base64url.baseDecode(x!) + [Domain.KeyProperties.rawKey]: base64url.baseDecode(x) }); } else if (crv === Domain.Curve.SECP256K1) { pk = ctx.Apollo.createPublicKey({ [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, [Domain.KeyProperties.type]: Domain.KeyTypes.EC, - [Domain.KeyProperties.rawKey]: base64url.baseDecode(x!) + [Domain.KeyProperties.rawKey]: base64url.baseDecode(x) }); } return pk; diff --git a/src/pollux/utils/jwt/index.ts b/src/pollux/utils/jwt/index.ts index efbb8bae9..a1e8d2f29 100644 --- a/src/pollux/utils/jwt/index.ts +++ b/src/pollux/utils/jwt/index.ts @@ -1,15 +1,2 @@ -import { Plugin } from "../../../plugins"; -import { JWT } from "./JWT"; -import { SDJWT } from "./SDJWT"; - -declare module "../../../utils/tasks" { - interface Extension extends IModule {} -} - -type IModule = Plugin.ExtractExtension; - -const module = new Plugin() - .extend("JWT", new JWT()) - .extend("SDJWT", new SDJWT()); - -export default module; +export * from "./JWT"; +export * from "./SDJWT"; diff --git a/src/utils/tasks.ts b/src/utils/tasks.ts index 3e061bc3e..b76725821 100644 --- a/src/utils/tasks.ts +++ b/src/utils/tasks.ts @@ -27,18 +27,18 @@ export abstract class Task { } } -/** - * used for a bit of TS hackery to enable Context extension - */ -// eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface Extension {} - export namespace Task { // ================================ // === Context === // ================================ + /** + * Context is provided to running tasks + * it optimistically provides access to Components + */ + export type Context = ContextProxy & Required & T; + export namespace Context { export type Options = Config & Base; @@ -47,25 +47,24 @@ export namespace Task { logLevel?: LogLevel; } - // dependencies + // base components export interface Base { Api?: Domain.Api; Apollo?: Domain.Apollo; Castor?: Domain.Castor; Mercury?: Domain.Mercury; - Pollux?: Domain.Pollux; Pluto?: Domain.Pluto; Seed?: Domain.Seed; } export const make = >(modules: T): Context => { const instance = new ContextProxy(modules); - return instance as any; + return instance as Context; }; } /** - * create a proxy so we can extend arbitrarily + * Context using proxy so we can extend arbitrarily */ class ContextProxy { public readonly logger: ILogger; @@ -101,6 +100,7 @@ export namespace Task { } } + // TODO improve with logging and error checking extend(deps: JsonObj): this { Object.entries(deps).forEach(([key, value]) => { this.modules[key] = value; @@ -110,11 +110,6 @@ export namespace Task { } } - /** - * - */ - export type Context = ContextProxy & Required & Extension & T; - export abstract class Runner { private context?: Context; diff --git a/tests/agent/Agent.ConnectionsManager.test.ts b/tests/agent/Agent.ConnectionsManager.test.ts index de1dde531..6d0bb1ed3 100644 --- a/tests/agent/Agent.ConnectionsManager.test.ts +++ b/tests/agent/Agent.ConnectionsManager.test.ts @@ -6,6 +6,7 @@ import { Apollo, BasicMediatorHandler, Castor, ConnectionsManager, MediatorStore import { Curve, KeyTypes, Mercury, Service, ServiceEndpoint } from "../../src/domain"; import { MercuryStub } from "./mocks/MercuryMock"; import { AgentOptions } from "../../src/edge-agent/types"; +import DIDCommAgent from '../../src/edge-agent/didcomm/Agent'; chai.use(SinonChai); chai.use(chaiAsPromised); @@ -18,16 +19,14 @@ const castor = new Castor(apollo) const pluto: Pluto = null as any; async function createBasicMediationHandler( - ConnectionsManager: any, - BasicMediatorHandler: any, + cmCtor: typeof ConnectionsManager, + mhCtor: typeof BasicMediatorHandler, services: Service[], options?: AgentOptions -): Promise< - { - manager: ConnectionsManager, - handler: BasicMediatorHandler - } -> { +): Promise<{ + manager: ConnectionsManager, + handler: BasicMediatorHandler +}> { const seed = apollo.createRandomSeed().seed; const keypair = apollo.createPrivateKey({ type: KeyTypes.EC, @@ -35,7 +34,7 @@ async function createBasicMediationHandler( seed: Buffer.from(seed.value).toString("hex"), }); const mediatorDID = await castor.createPrismDID(keypair.publicKey(), services); - const handler = new BasicMediatorHandler( + const handler = new mhCtor( mediatorDID, mercury, store @@ -46,20 +45,17 @@ async function createBasicMediationHandler( mediatorDID: mediatorDID } - const pollux: Pollux = null as any; - const manager = new ConnectionsManager( - castor, - mercury, - pluto, - pollux, - handler, - [], - options - ) - return { - manager, - handler - } + const pluto = null as any; + const agent = DIDCommAgent.initialize({ + mediatorDID, + pluto, + castor, + mercury, + }); + (agent as any).mediationHandler = handler; + const manager = new cmCtor(agent, options); + + return { manager, handler }; } @@ -191,7 +187,7 @@ describe("ConnectionsManager tests", () => { manager.stopFetchingMessages() }) - it("Should not use websockets if the mediator'd did endpoint uri does not contain ws or wss for more than the agent has opted in", async () => { + it("Should not use websockets if the mediator did endpoint uri does not contain ws or wss for more than the agent has opted in", async () => { const services = [ new Service( "#didcomm-1", diff --git a/tests/agent/Agent.anoncreds.test.ts b/tests/agent/Agent.anoncreds.test.ts index 24cb22620..a557968b8 100644 --- a/tests/agent/Agent.anoncreds.test.ts +++ b/tests/agent/Agent.anoncreds.test.ts @@ -31,14 +31,12 @@ import InMemoryStore from "../fixtures/inmemory"; import { ApiResponse, Pluto as IPluto, JWT } from "../../src/domain"; import { Pluto } from "../../src/pluto/Pluto"; import { Castor, Store } from "../../src"; -import { Pollux } from "../../src/"; import { randomUUID } from "crypto"; -import AnoncredsModule from "../../src/pollux/plugins/anoncreds/Plugin"; +import AnoncredsModule from "../../src/plugins/internal/anoncreds"; let agent: Agent; let pluto: IPluto; -let pollux: Pollux; let castor: CastorType; let store: Pluto.Store; let api: Api; @@ -76,7 +74,6 @@ describe("Agent Tests", () => { pluto = new Pluto(store, apollo); const mercury = new Mercury(castor, didProtocol, api); - // const polluxInstance = new Pollux(apollo, castor); const seed: Seed = { value: new Uint8Array([69, 191, 35, 232, 213, 102, 3, 93, 180, 106, 224, 144, 79, 171, 79, 223, 154, 217, 235, 232, 96, 30, 248, 92, 100, 38, 38, 42, 101, 53, 2, 247, 56, 111, 148, 220, 237, 122, 15, 120, 55, 82, 89, 150, 35, 45, 123, 135, 159, 140, 52, 127, 239, 148, 150, 109, 86, 145, 77, 109, 47, 60, 20, 16]) }; @@ -91,16 +88,7 @@ describe("Agent Tests", () => { api, }); - agent.register(AnoncredsModule); - - // vi.spyOn(agent.connectionManager, "startMediator").mockResolvedValue(); - // vi.spyOn(agent.connectionManager, "startFetchingMessages").mockResolvedValue(); - // (agent.mediationHandler as any).mediator = { - // hostDID: DID.from("did:peer:2.Ez6LSghwSE437wnDE1pt3X6hVDUQzSjsHzinpX3XFvMjRAm7y.Vz6Mkhh1e5CEYYq6JBUcTZ6Cp2ranCWRrv7Yax3Le4N59R6dd.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuNDQ6ODA4MCIsImEiOlsiZGlkY29tbS92MiJdfX0.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6IndzOi8vMTkyLjE2OC4xLjQ0OjgwODAvd3MiLCJhIjpbImRpZGNvbW0vdjIiXX19"), - // mediatorDID: DID.from("did:peer:2.Ez6LSghwSE437wnDE1pt3X6hVDUQzSjsHzinpX3XFvMjRAm7y.Vz6Mkhh1e5CEYYq6JBUcTZ6Cp2ranCWRrv7Yax3Le4N59R6dd.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuNDQ6ODA4MCIsImEiOlsiZGlkY29tbS92MiJdfX0.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6IndzOi8vMTkyLjE2OC4xLjQ0OjgwODAvd3MiLCJhIjpbImRpZGNvbW0vdjIiXX19"), - // routingDID: DID.from("did:peer:2.Ez6LSghwSE437wnDE1pt3X6hVDUQzSjsHzinpX3XFvMjRAm7y.Vz6Mkhh1e5CEYYq6JBUcTZ6Cp2ranCWRrv7Yax3Le4N59R6dd.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuNDQ6ODA4MCIsImEiOlsiZGlkY29tbS92MiJdfX0.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6IndzOi8vMTkyLjE2OC4xLjQ0OjgwODAvd3MiLCJhIjpbImRpZGNvbW0vdjIiXX19"), - // }; - + agent.plugins.register(AnoncredsModule); }); describe("Anoncreds", () => { diff --git a/tests/agent/Agent.functional.test.ts b/tests/agent/Agent.functional.test.ts index aff8a9196..12967aaae 100644 --- a/tests/agent/Agent.functional.test.ts +++ b/tests/agent/Agent.functional.test.ts @@ -27,23 +27,19 @@ describe("Agent", () => { describe("Persistence", () => { test("start() called for Startable dependencies", async () => { const spyPluto = vi.spyOn(agent.pluto, "start"); - const spyPollux = vi.spyOn(agent.pollux, "start"); await agent.start(); expect(spyPluto).toHaveBeenCalledOnce(); - expect(spyPollux).toHaveBeenCalledOnce(); }); test("stop() called for Startable dependencies", async () => { const spyPluto = vi.spyOn(agent.pluto, "stop"); - const spyPollux = vi.spyOn(agent.pollux, "stop"); await agent.start(); await agent.stop(); expect(spyPluto).toHaveBeenCalledOnce(); - expect(spyPollux).toHaveBeenCalledOnce(); }); diff --git a/tests/agent/Agent.test.ts b/tests/agent/Agent.test.ts index 09fe38738..fbc694eb7 100644 --- a/tests/agent/Agent.test.ts +++ b/tests/agent/Agent.test.ts @@ -12,7 +12,6 @@ import * as Fixtures from "../fixtures"; import { Api, AttachmentDescriptor, - AttachmentFormats, Credential, CredentialMetadata, CredentialType, @@ -38,25 +37,19 @@ import { Presentation } from "../../src/edge-agent/protocols/proofPresentation/P import { JWTCredential } from "../../src/pollux/models/JWTVerifiableCredential"; import { AnonCredsCredential } from "../../src/pollux/models/AnonCredsVerifiableCredential"; import InMemoryStore from "../fixtures/inmemory"; -import { ApiResponse, Pluto as IPluto } from "../../src/domain"; +import { ApiResponse, Pluto as IPluto, JWT } from "../../src/domain"; import { Pluto } from "../../src/pluto/Pluto"; import { RevocationNotification } from "../../src/edge-agent/protocols/revocation/RevocationNotfiication"; import { Castor, SDJWTCredential, Store } from "../../src"; -// import { randomUUID } from "crypto"; -import { JWT } from "../../src/pollux/utils/JWT"; -import { AgentBackup } from '../../src/edge-agent/Agent.Backup'; - - -// import { Castor, Store } from "../../src"; -import { Pollux } from "../../src/"; import { randomUUID } from "crypto"; +import { DIF } from '../../src/plugins/internal/dif/types'; chai.use(SinonChai); chai.use(chaiAsPromised); let agent: Agent; +let apollo: Apollo; let pluto: IPluto; -let pollux: Pollux; let castor: CastorType; let sandbox: sinon.SinonSandbox; let store: Pluto.Store; @@ -81,7 +74,7 @@ describe("Agent Tests", () => { })), })); sandbox = sinon.createSandbox(); - const apollo: Apollo = new Apollo(); + apollo = new Apollo(); castor = new Castor(apollo); api = { request: async () => new ApiResponse(new Uint8Array(), 200), @@ -723,6 +716,43 @@ describe("Agent Tests", () => { }); describe("initiatePresentationRequest", () => { + const expectedBody = { + presentation_definition: { + id: expect.stringMatching(""), + input_descriptors: [ + { + id: expect.stringMatching(""), + name: "Presentation", + purpose: "Verifying Credentials", + constraints: { + fields: [], + limit_disclosure: "required", + }, + format: { + jwt: { + alg: ["ES256K"], + }, + sdjwt: { + alg: ["ES256K"], + }, + }, + }, + ], + format: { + jwt: { + alg: ["ES256K"], + }, + sdjwt: { + alg: ["ES256K"], + }, + }, + }, + options: { + challenge: expect.stringContaining("Sign this text"), + domain: "N/A", + }, + }; + test("JWT", async () => { const result = await agent.initiatePresentationRequest(CredentialType.JWT, Fixtures.DIDs.peerDID1, { claims: {} }); @@ -730,43 +760,8 @@ describe("Agent Tests", () => { expect(result.attachments).toHaveLength(1); const attached = result.attachments[0]; expect(attached.id).to.be.a("string"); - expect(attached.format).toBe(AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS); + expect(attached.format).toBe(DIF.PRESENTATION_REQUEST); expect(attached.mediaType).toBe("application/json"); - const expectedBody = { - presentation_definition: { - id: expect.stringMatching(""), - input_descriptors: [ - { - id: expect.stringMatching(""), - name: "Presentation", - purpose: "Verifying Credentials", - constraints: { - fields: [ - ], - limit_disclosure: "required", - }, - format: { - jwt: { - alg: [ - "ES256K", - ], - }, - }, - }, - ], - format: { - jwt: { - alg: [ - "ES256K", - ], - }, - }, - }, - options: { - challenge: expect.stringContaining("Sign this text"), - domain: "N/A", - }, - }; // TODO fix types expect((attached.data as any).json).toEqual(expectedBody); @@ -780,40 +775,8 @@ describe("Agent Tests", () => { expect(result.attachments).toHaveLength(1); const attached = result.attachments[0]; expect(attached.id).to.be.a("string"); - expect(attached.format).toBe(AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS); + expect(attached.format).toBe(DIF.PRESENTATION_REQUEST); expect(attached.mediaType).toBe("application/json"); - const expectedBody = { - presentation_definition: { - id: expect.stringMatching(""), - input_descriptors: [ - { - id: expect.stringMatching(""), - name: "Presentation", - purpose: "Verifying Credentials", - constraints: { - fields: [ - ], - limit_disclosure: "required", - }, - format: { - sdjwt: { - alg: [ - "ES256K", - ], - }, - }, - }, - ], - format: { - sdjwt: { - alg: [ - "ES256K", - ], - }, - }, - }, - options: {}, - }; // TODO fix types expect((attached.data as any).json).toEqual(expectedBody); @@ -822,18 +785,6 @@ describe("Agent Tests", () => { }); describe("handlePresentation", () => { - const jwt = new JWT(new Apollo(), CastorMock); - const didstr = "did:prism:da61cf65fbf04b6b9fe06fa3b577fca3e05895a13902decaad419845a20d2d78:Ct8BCtwBEnQKH2F1dGhlbnRpY2F0aW9uYXV0aGVudGljYXRpb25LZXkQBEJPCglzZWNwMjU2azESIP0gMhTAVOk7SgWRluzmeJIjtm2-YMc6AbrD3ePKJQj-GiDZlsa5pQuXGzKvgK10D8SzuDvh79u5oMB7-ZeJNAh-ixJkCg9tYXN0ZXJtYXN0ZXJLZXkQAUJPCglzZWNwMjU2azESIP0gMhTAVOk7SgWRluzmeJIjtm2-YMc6AbrD3ePKJQj-GiDZlsa5pQuXGzKvgK10D8SzuDvh79u5oMB7-ZeJNAh-iw"; - const payload: JWTCredentialPayload = { - iss: didstr, - sub: didstr, - nbf: 23456754321, - exp: 2134564321, - vc: { - credentialSubject: { test: "did:prism" } - } as any - }; - beforeEach(() => { sandbox.stub(pluto, "getDIDPrivateKeysByDID") .resolves([Fixtures.Keys.secp256K1.privateKey]); @@ -849,13 +800,7 @@ describe("Agent Tests", () => { } }); - const jwtString = await jwt.sign({ - issuerDID: DID.fromString(didstr), - privateKey: Fixtures.Keys.secp256K1.privateKey, - payload: payload, - }); - - const credential = JWTCredential.fromJWS(jwtString); + const credential = JWTCredential.fromJWS(Fixtures.Credentials.JWT.credentialData.jws); const presentation = await agent.createPresentationForRequestProof(presentationReq, credential); const result = await agent.handlePresentation(presentation); @@ -872,45 +817,39 @@ describe("Agent Tests", () => { } }); - const jwtString = await jwt.sign({ - issuerDID: DID.fromString(didstr), - privateKey: Fixtures.Keys.secp256K1.privateKey, - payload: payload, - }); - - const credential = SDJWTCredential.fromJWS(jwtString); + const credential = SDJWTCredential.fromJWS(Fixtures.Credentials.JWT.credentialData.jws); const presentation = await agent.createPresentationForRequestProof(presentationReq, credential); const result = await agent.handlePresentation(presentation); expect(result).toBe(true); }); - test("Anoncreds", async () => { - sandbox.stub(pluto, "getLinkSecret").resolves(Fixtures.Credentials.Anoncreds.linkSecret); + // test("Anoncreds", async () => { + // sandbox.stub(pluto, "getLinkSecret").resolves(Fixtures.Credentials.Anoncreds.linkSecret); - sandbox.stub(pollux as any, "fetchSchema") - .resolves(Fixtures.Credentials.Anoncreds.schema); + // sandbox.stub(pollux as any, "fetchSchema") + // .resolves(Fixtures.Credentials.Anoncreds.schema); - sandbox.stub(pollux as any, "fetchCredentialDefinition") - .resolves(Fixtures.Credentials.Anoncreds.credentialDefinition); + // sandbox.stub(pollux as any, "fetchCredentialDefinition") + // .resolves(Fixtures.Credentials.Anoncreds.credentialDefinition); - const presentationReq = await agent.initiatePresentationRequest(CredentialType.AnonCreds, Fixtures.DIDs.peerDID1, { - attributes: { - attr1_referent: { - name: "name", - restrictions: { - cred_def_id: "did:web:xyz/resource/definition", - }, - }, - } - }); + // const presentationReq = await agent.initiatePresentationRequest(CredentialType.AnonCreds, Fixtures.DIDs.peerDID1, { + // attributes: { + // attr1_referent: { + // name: "name", + // restrictions: { + // cred_def_id: "did:web:xyz/resource/definition", + // }, + // }, + // } + // }); - const credential = new AnonCredsCredential(Fixtures.Credentials.Anoncreds.credential); - const presentation = await agent.createPresentationForRequestProof(presentationReq, credential); - const result = await agent.handlePresentation(presentation); + // const credential = new AnonCredsCredential(Fixtures.Credentials.Anoncreds.credential); + // const presentation = await agent.createPresentationForRequestProof(presentationReq, credential); + // const result = await agent.handlePresentation(presentation); - expect(result).toBe(true); - }); + // expect(result).toBe(true); + // }); }); }); }); diff --git a/tests/agent/didcomm/Agent.functional.test.ts b/tests/agent/didcomm/Agent.functional.test.ts index baac20d86..dd3541b2f 100644 --- a/tests/agent/didcomm/Agent.functional.test.ts +++ b/tests/agent/didcomm/Agent.functional.test.ts @@ -28,21 +28,18 @@ describe("Agent", () => { describe("Persistence", () => { test("start() called for Startable dependencies", async () => { const spyPluto = vi.spyOn(agent.pluto, "start"); - const spyPollux = vi.spyOn(agent.pollux, "start"); const spyMediator = vi.spyOn(agent.connectionManager, "startMediator"); const spyMessages = vi.spyOn(agent.connectionManager, "startFetchingMessages"); await agent.start(); expect(spyPluto).toHaveBeenCalledOnce(); - expect(spyPollux).toHaveBeenCalledOnce(); expect(spyMediator).toHaveBeenCalledOnce(); expect(spyMessages).toHaveBeenCalledOnce(); }); test("calling start() twice should not throw", async () => { const spyPluto = vi.spyOn(agent.pluto, "start"); - const spyPollux = vi.spyOn(agent.pollux, "start"); const spyMediator = vi.spyOn(agent.connectionManager, "startMediator"); const spyMessages = vi.spyOn(agent.connectionManager, "startFetchingMessages"); @@ -50,14 +47,12 @@ describe("Agent", () => { await agent.start(); expect(spyPluto).toHaveBeenCalledOnce(); - expect(spyPollux).toHaveBeenCalledOnce(); expect(spyMediator).toHaveBeenCalledOnce(); expect(spyMessages).toHaveBeenCalledOnce(); }); test("stop() called for Startable dependencies", async () => { const spyPluto = vi.spyOn(agent.pluto, "stop"); - const spyPollux = vi.spyOn(agent.pollux, "stop"); const spyEvents = vi.spyOn(agent.connectionManager, "stopAllEvents"); const spyMessages = vi.spyOn(agent.connectionManager, "stopFetchingMessages"); @@ -65,7 +60,6 @@ describe("Agent", () => { await agent.stop(); expect(spyPluto).toHaveBeenCalledOnce(); - expect(spyPollux).toHaveBeenCalledOnce(); expect(spyEvents).toHaveBeenCalledOnce(); expect(spyMessages).toHaveBeenCalledOnce(); }); diff --git a/tests/agent/didcomm/invitation.test.ts b/tests/agent/didcomm/invitation.test.ts index 527110b3b..b59df0c58 100644 --- a/tests/agent/didcomm/invitation.test.ts +++ b/tests/agent/didcomm/invitation.test.ts @@ -42,7 +42,6 @@ describe("Agent", () => { routingDID: DID.from("did:peer:2.Ez6LSghwSE437wnDE1pt3X6hVDUQzSjsHzinpX3XFvMjRAm7y.Vz6Mkhh1e5CEYYq6JBUcTZ6Cp2ranCWRrv7Yax3Le4N59R6dd.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuNDQ6ODA4MCIsImEiOlsiZGlkY29tbS92MiJdfX0.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6IndzOi8vMTkyLjE2OC4xLjQ0OjgwODAvd3MiLCJhIjpbImRpZGNvbW0vdjIiXX19"), }; - await agent.pollux.start(); await agent.start(); }); diff --git a/tests/agent/oidc/Agent.functional.test.ts b/tests/agent/oidc/Agent.functional.test.ts index eb5ca2c1f..57fc85842 100644 --- a/tests/agent/oidc/Agent.functional.test.ts +++ b/tests/agent/oidc/Agent.functional.test.ts @@ -18,34 +18,28 @@ describe("Agent", () => { describe("Persistence", () => { test("start() called for Startable dependencies", async () => { const spyPluto = vi.spyOn(agent.pluto, "start"); - const spyPollux = vi.spyOn(agent.pollux, "start"); await agent.start(); expect(spyPluto).toHaveBeenCalledOnce(); - expect(spyPollux).toHaveBeenCalledOnce(); }); test("calling start() twice should not throw", async () => { const spyPluto = vi.spyOn(agent.pluto, "start"); - const spyPollux = vi.spyOn(agent.pollux, "start"); await agent.start(); await agent.start(); expect(spyPluto).toHaveBeenCalledOnce(); - expect(spyPollux).toHaveBeenCalledOnce(); }); test("stop() called for Startable dependencies", async () => { const spyPluto = vi.spyOn(agent.pluto, "stop"); - const spyPollux = vi.spyOn(agent.pollux, "stop"); await agent.start(); await agent.stop(); expect(spyPluto).toHaveBeenCalledOnce(); - expect(spyPollux).toHaveBeenCalledOnce(); }); test("Start > Stop > Start - should run without errors", async () => { diff --git a/tests/agent/oidc/task.ParseCredentialOffer.test.ts b/tests/agent/oidc/task.ParseCredentialOffer.test.ts index f93274110..89d1dad14 100644 --- a/tests/agent/oidc/task.ParseCredentialOffer.test.ts +++ b/tests/agent/oidc/task.ParseCredentialOffer.test.ts @@ -18,7 +18,7 @@ describe("OIDC Tasks", () => { beforeEach(() => { sandbox = sinon.createSandbox(); - ctx = Task.Context.make({ + ctx = Task.Context.make({ Api: { request: vi.fn() } }); }); @@ -28,7 +28,7 @@ describe("OIDC Tasks", () => { }); describe("parseCredentialOffer", () => { - test.only("valid offer - json - returns offer", async () => { + test("valid offer - json - returns offer", async () => { const task = new ParseCredentialOffer({ value: Fixtures.OIDC.credentialOfferJson }); const result = await ctx.run(task); expect(result).to.deep.eq(Fixtures.OIDC.credentialOfferJson); diff --git a/tests/fixtures/credentials/jwt.ts b/tests/fixtures/credentials/jwt.ts index 2ba0db9cd..4ff2505ee 100644 --- a/tests/fixtures/credentials/jwt.ts +++ b/tests/fixtures/credentials/jwt.ts @@ -66,7 +66,8 @@ export const credential: W3CVerifiableCredential = { type: [W3CVerifiableCredentialType.credential], "@context": [W3CVerifiableCredentialContext.credential], credentialSubject: { - additionalProp2: 'Test3', id: 'did:prism:beea5234af46804714d8ea8ec77b66cc7f3e815c68abb475f254cf9c30626763:CscBCsQBEmQKD2F1dGhlbnRpY2F0aW9uMBAEQk8KCXNlY3AyNTZrMRIgeSg-2OO1JdnpzUOBitzIicXdfzeAcTfWAN-YCeuCbyIaIJQ4GTI30taViwchT3e0nLXBS43B4j9jlslKo2ZldXzjElwKB21hc3RlcjAQAUJPCglzZWNwMjU2azESIHkoPtjjtSXZ6c1DgYrcyInF3X83gHE31gDfmAnrgm8iGiCUOBkyN9LWlYsHIU93tJy1wUuNweI_Y5bJSqNmZXV84w' + additionalProp2: 'Test3', + id: 'did:prism:beea5234af46804714d8ea8ec77b66cc7f3e815c68abb475f254cf9c30626763:CscBCsQBEmQKD2F1dGhlbnRpY2F0aW9uMBAEQk8KCXNlY3AyNTZrMRIgeSg-2OO1JdnpzUOBitzIicXdfzeAcTfWAN-YCeuCbyIaIJQ4GTI30taViwchT3e0nLXBS43B4j9jlslKo2ZldXzjElwKB21hc3RlcjAQAUJPCglzZWNwMjU2azESIHkoPtjjtSXZ6c1DgYrcyInF3X83gHE31gDfmAnrgm8iGiCUOBkyN9LWlYsHIU93tJy1wUuNweI_Y5bJSqNmZXV84w' }, expirationDate: new Date(1685635595).toISOString(), issuanceDate: new Date(1685631995).toISOString(), @@ -91,3 +92,25 @@ export const presentationRequest = { "domain": "http://localhost:8000/prism-agent" } }; + + +const didstr = "did:prism:da61cf65fbf04b6b9fe06fa3b577fca3e05895a13902decaad419845a20d2d78:Ct8BCtwBEnQKH2F1dGhlbnRpY2F0aW9uYXV0aGVudGljYXRpb25LZXkQBEJPCglzZWNwMjU2azESIP0gMhTAVOk7SgWRluzmeJIjtm2-YMc6AbrD3ePKJQj-GiDZlsa5pQuXGzKvgK10D8SzuDvh79u5oMB7-ZeJNAh-ixJkCg9tYXN0ZXJtYXN0ZXJLZXkQAUJPCglzZWNwMjU2azESIP0gMhTAVOk7SgWRluzmeJIjtm2-YMc6AbrD3ePKJQj-GiDZlsa5pQuXGzKvgK10D8SzuDvh79u5oMB7-ZeJNAh-iw"; +// jwtstr created with: +// const jwtStr = await jwt.sign({ +// issuerDID: DID.fromString(didstr), +// privateKey: Fixtures.Keys.secp256K1.privateKey, +// payload: payload, +// }); +export const credentialData = { + didstr, + jws: "eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MzYzMzAzNjUsImV4cCI6MjEzNDU2NDMyMSwiaXNzIjoiZGlkOnByaXNtOmRhNjFjZjY1ZmJmMDRiNmI5ZmUwNmZhM2I1NzdmY2EzZTA1ODk1YTEzOTAyZGVjYWFkNDE5ODQ1YTIwZDJkNzg6Q3Q4QkN0d0JFblFLSDJGMWRHaGxiblJwWTJGMGFXOXVZWFYwYUdWdWRHbGpZWFJwYjI1TFpYa1FCRUpQQ2dselpXTndNalUyYXpFU0lQMGdNaFRBVk9rN1NnV1JsdXptZUpJanRtMi1ZTWM2QWJyRDNlUEtKUWotR2lEWmxzYTVwUXVYR3pLdmdLMTBEOFN6dUR2aDc5dTVvTUI3LVplSk5BaC1peEprQ2c5dFlYTjBaWEp0WVhOMFpYSkxaWGtRQVVKUENnbHpaV053TWpVMmF6RVNJUDBnTWhUQVZPazdTZ1dSbHV6bWVKSWp0bTItWU1jNkFickQzZVBLSlFqLUdpRFpsc2E1cFF1WEd6S3ZnSzEwRDhTenVEdmg3OXU1b01CNy1aZUpOQWgtaXciLCJzdWIiOiJkaWQ6cHJpc206ZGE2MWNmNjVmYmYwNGI2YjlmZTA2ZmEzYjU3N2ZjYTNlMDU4OTVhMTM5MDJkZWNhYWQ0MTk4NDVhMjBkMmQ3ODpDdDhCQ3R3QkVuUUtIMkYxZEdobGJuUnBZMkYwYVc5dVlYVjBhR1Z1ZEdsallYUnBiMjVMWlhrUUJFSlBDZ2x6WldOd01qVTJhekVTSVAwZ01oVEFWT2s3U2dXUmx1em1lSklqdG0yLVlNYzZBYnJEM2VQS0pRai1HaURabHNhNXBRdVhHekt2Z0sxMEQ4U3p1RHZoNzl1NW9NQjctWmVKTkFoLWl4SmtDZzl0WVhOMFpYSnRZWE4wWlhKTFpYa1FBVUpQQ2dselpXTndNalUyYXpFU0lQMGdNaFRBVk9rN1NnV1JsdXptZUpJanRtMi1ZTWM2QWJyRDNlUEtKUWotR2lEWmxzYTVwUXVYR3pLdmdLMTBEOFN6dUR2aDc5dTVvTUI3LVplSk5BaC1pdyIsIm5iZiI6MjM0NTY3NTQzMjEsInZjIjp7ImNyZWRlbnRpYWxTdWJqZWN0Ijp7InRlc3QiOiJkaWQ6cHJpc20ifX19.4FaQYOr99ED3J4wwQTTV9f3WjxmDr9YtIFuNNAEK8EYcgprrrEO--NMVUH23wkOst1l3FNM9GgP9hZq8dGLBlg", + payload: { + iss: didstr, + sub: didstr, + nbf: 23456754321, + exp: 2134564321, + vc: { + credentialSubject: { test: "did:prism" } + } + }, +}; diff --git a/tests/plugins/anoncreds/AnoncredsLoader.test.ts b/tests/plugins/anoncreds/AnoncredsLoader.test.ts new file mode 100644 index 000000000..4f13300c0 --- /dev/null +++ b/tests/plugins/anoncreds/AnoncredsLoader.test.ts @@ -0,0 +1,175 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { AnoncredsLoader } from '../../../src/plugins/internal/anoncreds/module/AnoncredsLoader'; +import * as Fixtures from "../../fixtures"; + +describe("Plugins - Anoncreds", () => { + let anoncreds: AnoncredsLoader; + + beforeEach(() => { + anoncreds = new AnoncredsLoader(); + }); + + describe("Anoncreds", () => { + beforeEach(async () => { + await (anoncreds as any).load(); + }); + + test("createLinkSecret", async () => { + const result = await anoncreds.createLinksecret(); + expect(result).to.be.a("string"); + }); + + test("createCredentialRequest", async () => { + const result = await anoncreds.createCredentialRequest( + Fixtures.Credentials.Anoncreds.credentialOffer, + Fixtures.Credentials.Anoncreds.credentialDefinition, + Fixtures.Credentials.Anoncreds.linkSecret.secret, + Fixtures.Credentials.Anoncreds.linkSecret.name, + ); + + expect(result).to.be.an("array").to.have.length(2); + // CredentialRequest + const credReq = result[0]; + expect(credReq).to.have.property("cred_def_id").to.be.a("string"); + expect(credReq).to.have.property("entropy").to.be.a("string"); + expect(credReq).to.have.property("nonce").to.be.a("string"); + expect(credReq).to.have.property("blinded_ms"); + expect(credReq.blinded_ms).to.have.property("u"); + expect(credReq.blinded_ms).to.have.property("hidden_attributes"); + expect(credReq.blinded_ms).to.have.property("committed_attributes"); + expect(credReq).to.have.property("blinded_ms_correctness_proof"); + expect(credReq.blinded_ms_correctness_proof).to.have.property("c"); + expect(credReq.blinded_ms_correctness_proof).to.have.property("v_dash_cap"); + expect(credReq.blinded_ms_correctness_proof).to.have.property("m_caps"); + expect(credReq.blinded_ms_correctness_proof).to.have.property("r_caps"); + + // CredentialRequestMeta + const credReqMeta = result[1]; + expect(credReqMeta).to.have.property("link_secret_blinding_data"); + expect(credReqMeta.link_secret_blinding_data) + .to.have.property("v_prime") + .to.be.a("string"); + expect(credReqMeta) + .to.have.property("link_secret_name") + .to.be.a("string"); + expect(credReqMeta).to.have.property("nonce").to.be.a("string"); + }); + + test("createPresentation", async () => { + const result = await anoncreds.createPresentation( + Fixtures.Credentials.Anoncreds.presentationRequest, + Fixtures.Credentials.Anoncreds.schemas, + Fixtures.Credentials.Anoncreds.credDefs, + Fixtures.Credentials.Anoncreds.credential, + Fixtures.Credentials.Anoncreds.linkSecret.secret + ); + + expect(result).to.be.an("object"); + expect(result).to.have.property("proof"); + expect(result.proof) + .to.have.property("proofs") + .to.be.an("array") + .to.have.length(1); + + result.proof.proofs.forEach((proof) => { + expect(proof).to.have.property("primary_proof"); + expect(proof.primary_proof).to.have.property("eq_proof"); + expect(proof.primary_proof.eq_proof).to.have.property("revealed_attrs"); + expect(proof.primary_proof.eq_proof.revealed_attrs) + .to.have.property("name") + .to.be.a("string"); + expect(proof.primary_proof.eq_proof) + .to.have.property("a_prime") + .to.be.a("string"); + expect(proof.primary_proof.eq_proof) + .to.have.property("e") + .to.be.a("string"); + expect(proof.primary_proof.eq_proof) + .to.have.property("m") + .to.be.an("object"); + expect(proof.primary_proof.eq_proof.m) + .to.have.property("age") + .to.be.an("string"); + expect(proof.primary_proof.eq_proof.m) + .to.have.property("master_secret") + .to.be.an("string"); + expect(proof.primary_proof.eq_proof) + .to.have.property("m2") + .to.be.a("string"); + expect(proof.primary_proof.eq_proof) + .to.have.property("v") + .to.be.a("string"); + + expect(proof.primary_proof) + .to.have.property("ge_proofs") + .to.be.an("array") + .to.have.length(1); + + proof.primary_proof.ge_proofs.forEach((geProof) => { + expect(geProof).to.have.property("mj").to.be.a("string"); + expect(geProof).to.have.property("alpha").to.be.a("string"); + expect(geProof).to.have.property("r").to.be.an("object"); + expect(geProof).to.have.property("t").to.be.an("object"); + expect(geProof).to.have.property("u").to.be.an("object"); + expect(geProof).to.have.property("predicate").to.be.an("object"); + expect(geProof.predicate).to.have.property( + "attr_name", + Fixtures.Credentials.Anoncreds.presentationRequest.requested_predicates.age1.name + ); + expect(geProof.predicate).to.have.property("p_type", "GE"); + expect(geProof.predicate).to.have.property( + "value", + Fixtures.Credentials.Anoncreds.presentationRequest.requested_predicates.age1.p_value + ); + }); + }); + + expect(result.proof).to.have.property("aggregated_proof"); + expect(result.proof.aggregated_proof) + .to.have.property("c_hash") + .to.be.a("string"); + expect(result.proof.aggregated_proof) + .to.have.property("c_list") + .to.be.an("array"); + + expect(result).to.have.property("requested_proof"); + expect(result.requested_proof).to.have.property("predicates"); + expect(result.requested_proof).to.have.property("revealed_attrs"); + expect(result.requested_proof).to.have.property("self_attested_attrs"); + expect(result.requested_proof).to.have.property("unrevealed_attrs"); + + expect(result) + .to.have.property("identifiers") + .to.be.an("array") + .to.have.length(1); + + result.identifiers.forEach((identifier) => { + expect(identifier).to.have.property("schema_id", Fixtures.Credentials.Anoncreds.schemaId); + expect(identifier).to.have.property("cred_def_id", Fixtures.Credentials.Anoncreds.credDefId); + }); + }); + + test("processCredential", async () => { + const result = await anoncreds.processCredential( + Fixtures.Credentials.Anoncreds.credentialDefinition, + Fixtures.Credentials.Anoncreds.credentialIssued, + Fixtures.Credentials.Anoncreds.credentialRequestMeta, + Fixtures.Credentials.Anoncreds.linkSecret.secret + ); + + expect(result).to.have.property("schema_id", Fixtures.Credentials.Anoncreds.schemaId); + expect(result).to.have.property("cred_def_id", Fixtures.Credentials.Anoncreds.credDefId); + expect(result).to.have.property("signature"); + expect(result).to.have.property("signature_correctness_proof"); + + expect(result.values) + .to.have.property("age") + .to.deep.equal(Fixtures.Credentials.Anoncreds.credentialIssued.values.age); + + expect(result.values) + .to.have.property("name") + .to.deep.equal(Fixtures.Credentials.Anoncreds.credentialIssued.values.name); + + }); + }); +}); diff --git a/tests/plugins/anoncreds/PresentationRequest.test.ts b/tests/plugins/anoncreds/PresentationRequest.test.ts new file mode 100644 index 000000000..b1daf4286 --- /dev/null +++ b/tests/plugins/anoncreds/PresentationRequest.test.ts @@ -0,0 +1,276 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { Task } from '../../../src/utils'; +import { AnonCredsCredential, JWTCredential, Pluto } from '../../../src'; +import { PresentationRequest } from '../../../src/plugins/internal/anoncreds/PresentationRequest'; +import * as FetchCredModule from "../../../src/plugins/internal/anoncreds/FetchCredentialDefinition"; +import * as FetchSchemaModule from "../../../src/plugins/internal/anoncreds/FetchSchema"; +import * as Fixtures from "../../fixtures"; +import { mockPluto } from '../../fixtures/inmemory/factory'; +import { AnoncredsLoader } from '../../../src/plugins/internal/anoncreds/module/AnoncredsLoader'; +import * as Anoncreds from "../../../src/plugins/internal/anoncreds/types"; + +describe("Plugins - Anoncreds", () => { + let ctx: Task.Context; + let pluto: Pluto; + + beforeEach(() => { + pluto = mockPluto(); + ctx = Task.Context.make({ + Pluto: pluto, + Anoncreds: new AnoncredsLoader(), + }); + + vi.spyOn(pluto, "getLinkSecret").mockResolvedValue(Fixtures.Credentials.Anoncreds.linkSecret); + vi.spyOn(FetchCredModule, "FetchCredentialDefinition").mockReturnValue({ + run: () => Promise.resolve(Fixtures.Credentials.Anoncreds.credentialDefinition), + log: () => ({}), + } as any); + vi.spyOn(FetchSchemaModule, "fetchSchema").mockReturnValue({ + run: () => Promise.resolve(Fixtures.Credentials.Anoncreds.schema), + log: () => ({}), + } as any); + }); + + describe("PresentationRequest", () => { + test("returns a Payload ", async () => { + + const credential = new AnonCredsCredential(Fixtures.Credentials.Anoncreds.credential); + const presentationRequest = { + nonce: "742809673342769044929402", + name: "example_presentation_request", + version: "0.1", + requested_attributes: { + attr1_referent: { + name: "name", + restrictions: { + cred_def_id: "did:web:xyz/resource/cred-def", + }, + }, + }, + requested_predicates: { + predicate1_referent: { + name: "age", + p_type: ">=", + p_value: 18, + // restrictions: [] + }, + }, + }; + + const result = await ctx.run(new PresentationRequest({ credential, presentationRequest })); + + expect(result.pid).toBe(Anoncreds.PRESENTATION); + expect(result.data).toEqual({ + proof: { + proofs: [{ + primary_proof: { + eq_proof: { + revealed_attrs: { + name: expect.stringMatching(/\d/), + }, + a_prime: expect.stringMatching(/\d/), + e: expect.stringMatching(/\d/), + v: expect.stringMatching(/\d/), + m: { + age: expect.stringMatching(/\d/), + master_secret: expect.stringMatching(/\d/), + }, + m2: expect.stringMatching(/\d/), + }, + ge_proofs: [ + { + u: { + "0": expect.stringMatching(/\d/), + "1": expect.stringMatching(/\d/), + "2": expect.stringMatching(/\d/), + "3": expect.stringMatching(/\d/), + }, + r: { + "0": expect.stringMatching(/\d/), + "1": expect.stringMatching(/\d/), + "2": expect.stringMatching(/\d/), + "3": expect.stringMatching(/\d/), + DELTA: expect.stringMatching(/\d/), + }, + mj: expect.stringMatching(/\d/), + alpha: expect.stringMatching(/\d/), + t: { + "0": expect.stringMatching(/\d/), + "1": expect.stringMatching(/\d/), + "2": expect.stringMatching(/\d/), + "3": expect.stringMatching(/\d/), + DELTA: expect.stringMatching(/\d/), + }, + predicate: { + attr_name: "age", + p_type: "GE", + value: 18, + }, + }, + ], + }, + }, + ], + aggregated_proof: { + c_hash: expect.stringMatching(/\d/), + c_list: [ + expect.arrayContaining([]), + expect.arrayContaining([]), + expect.arrayContaining([]), + expect.arrayContaining([]), + expect.arrayContaining([]), + expect.arrayContaining([]), + ], + }, + }, + requested_proof: { + revealed_attrs: { + attr1_referent: { + sub_proof_index: 0, + raw: "test", + encoded: expect.stringMatching(/\d/), + }, + }, + self_attested_attrs: {}, + unrevealed_attrs: {}, + predicates: { + predicate1_referent: { + sub_proof_index: 0, + }, + }, + }, + identifiers: [{ + cred_def_id: Fixtures.Credentials.Anoncreds.credDefId, + schema_id: Fixtures.Credentials.Anoncreds.schemaId, + }], + }); + }); + + test("Should Reject Creating a Presentation with a Credential that doesn't have the requested field 'email'", async () => { + const credential = new AnonCredsCredential(Fixtures.Credentials.Anoncreds.credential); + const presentationRequest = { + nonce: "742809673342769044929402", + name: "example_presentation_request", + version: "0.1", + requested_attributes: { + attr1_referent: { + name: "name", + restrictions: { + cred_def_id: "did:web:xyz/resource/cred-def", + }, + }, + attr2_referent: { + name: "email", + restrictions: { + cred_def_id: "did:web:xyz/resource/cred-def", + }, + }, + }, + requested_predicates: { + predicate1_referent: { + name: "age", + p_type: ">=", + p_value: 18, + // restrictions: [] + }, + }, + }; + + const sut = ctx.run(new PresentationRequest({ credential, presentationRequest })); + + await expect(sut).rejects.toThrowError('AnoncredsError Credential value not found for attribute "email"'); + }); + + test("Should Reject Creating a Presentation with a Credential that doesn't have predicates", async () => { + const credential = new AnonCredsCredential(Fixtures.Credentials.Anoncreds.credential); + const presentationRequest = { + nonce: "742809673342769044929402", + name: "example_presentation_request", + version: "0.1", + requested_attributes: { + // attr1_referent: { + // name: "name", + // restrictions: { + // cred_def_id: "did:web:xyz/resource/cred-def", + // }, + // }, + }, + requested_predicates: {}, + }; + + const sut = ctx.run(new PresentationRequest({ credential, presentationRequest })); + + await expect(sut).rejects.toThrowError("AnoncredsError No credential mapping or self-attested attributes presented"); + }); + + test("Should Reject Creating a Presentation with a Credential that doesn't have valid predicates", async () => { + const credential = new AnonCredsCredential(Fixtures.Credentials.Anoncreds.credential); + const presentationRequest = { + nonce: "742809673342769044929402", + name: "example_presentation_request", + version: "0.1", + requested_attributes: { + attr1_referent: { + name: "name", + restrictions: { + cred_def_id: "did:web:xyz/resource/cred-def", + }, + }, + }, + requested_predicates: { + predicate1_referent: { + name: "age", + p_type: ">=", + p_value: 50, + }, + }, + }; + + const sut = ctx.run(new PresentationRequest({ credential, presentationRequest })); + + expect(sut).rejects.toThrowError("AnoncredsError Error: Invalid structure\nCaused by: Predicate is not satisfied\n"); + }); + + test("Should Reject Creating an Anoncreds Presentation Submission using an invalid presentationDefinition", async () => { + const credential = new AnonCredsCredential(Fixtures.Credentials.Anoncreds.credential); + + const sut = ctx.run(new PresentationRequest({ credential, presentationRequest: null as any })); + + expect(sut).rejects.toThrowError('Invalid Presentation definition error'); + }); + + test("Should Reject Creating an Anoncreds Presentation Submission using a wrong JWT Credential", async () => { + const credential = new JWTCredential(Fixtures.Credentials.JWT.credentialPayload) as any; + const presentationRequest = { + nonce: "742809673342769044929402", + name: "example_presentation_request", + version: "0.1", + requested_attributes: { + attr1_referent: { + name: "name", + restrictions: { + cred_def_id: "did:web:xyz/resource/cred-def", + }, + }, + attr2_referent: { + name: "email", + restrictions: { + cred_def_id: "did:web:xyz/resource/cred-def", + }, + }, + }, + requested_predicates: { + predicate1_referent: { + name: "age", + p_type: ">=", + p_value: 18, + }, + }, + }; + + const sut = ctx.run(new PresentationRequest({ credential, presentationRequest })); + + expect(sut).rejects.toThrowError('Required a valid Anoncreds Credential for Anoncreds Presentation submission'); + }); + }); +}); diff --git a/tests/plugins/anoncreds/PresentationVerify.test.ts b/tests/plugins/anoncreds/PresentationVerify.test.ts new file mode 100644 index 000000000..d4a51b8a2 --- /dev/null +++ b/tests/plugins/anoncreds/PresentationVerify.test.ts @@ -0,0 +1,1769 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { Task } from '../../../src/utils'; +import * as FetchCredModule from "../../../src/plugins/internal/anoncreds/FetchCredentialDefinition"; +import * as FetchSchemaModule from "../../../src/plugins/internal/anoncreds/FetchSchema"; +import * as Fixtures from "../../fixtures"; +import { mockPluto } from '../../fixtures/inmemory/factory'; +import { AnoncredsLoader } from '../../../src/plugins/internal/anoncreds/module/AnoncredsLoader'; +import { PresentationVerify } from '../../../src/plugins/internal/anoncreds/PresentationVerify'; +import { Pluto, RequestPresentation } from '../../../src'; +import { AttachmentDescriptor, Message } from '../../../src/domain'; + +describe("Plugins - Anoncreds", () => { + let ctx: Task.Context; + let pluto: Pluto; + + beforeEach(() => { + pluto = mockPluto(); + ctx = Task.Context.make({ + Pluto: pluto, + Anoncreds: new AnoncredsLoader(), + }); + + vi.spyOn(pluto, "getLinkSecret").mockResolvedValue(Fixtures.Credentials.Anoncreds.linkSecret); + vi.spyOn(FetchCredModule, "FetchCredentialDefinition").mockReturnValue({ + run: () => Promise.resolve(Fixtures.Credentials.Anoncreds.credentialDefinition), + log: () => ({}), + } as any); + vi.spyOn(FetchSchemaModule, "fetchSchema").mockReturnValue({ + run: () => Promise.resolve(Fixtures.Credentials.Anoncreds.schema), + log: () => ({}), + } as any); + }); + + describe("PresentationVerify", () => { + const thid = "test-thread-id"; + + const presentation = { + proof: { + proofs: [ + { + primary_proof: { + eq_proof: { + revealed_attrs: { + name: "72155939486846849509759369733266486982821795810448245423168957390607644363272", + }, + a_prime: "1685349013305675563293969035056369165934573444034332453160671145408899281009216165537722437634253394637753857306634948317455386242636115590889617431055039494551485088595590475577085694765411751666512548092003393491081915912434945779876447738408988662031447174300113745188027849538170067041054193019258645040544910619590444953761422249034443071689144397226728862057015705976417255456174121613562052157416018482755806790235833144688060315758243165119439836629307729955161538837905610068581029221118274235280878407483246680481988437404550805757981194067286652983852027538454615556646765021375785185987196510135519831734", + e: "47936763190066143132672417795060724519732624058204598505364871455548305903706477166839143015639631335391813848000841462277270399355799539", + v: "1210331878579263238781603670601931740256122135224384246664831571797073449842338579440966958011500931279701619283937733531450463718996773531096328363297073984244285477863122455184659236409470729866959699786081924999893417316078383729438705685198591150933650658374693920950223397585890108110830964954566636206426613324574750189015603440599153225038674560249773715912624059210022344772016192756086093722907538768663474064797406191577579349993257395479084291509687759233899726925717885001217975078277194984491875977164216528443273771927861868618909655802633051431043158117714580447936185831374710262590158065125037833516961883725238036967870098944835893835175068566022838107296083994719864494999825676564803334888697298611122017884404866567828887201799137859162263994715052543730469250867288034579195442333826566196857798937570222864552780746824370690041363144809181132552393140687951526977546248489464941071226320174677861667", + m: { + age: "8131649707814904014141426751861254140244857332050317458841503042110216419093481757473733625323123641731496276277960922466302979265578851464885510284825995299446748135937916977603", + master_secret: "101502322943819056401353735706848541162970318151419480721949976358635148466757218851908575316895697424826242166317182465795714520313112493273806927395865125178456292605183853922", + }, + m2: "12630926983132400131885933325054142770527086008376407819050806926263954723281544242594503900294395544344500466277691681578863849463290641904946460124408923543881299225884172861033", + }, + ge_proofs: [ + { + u: { + "0": "13594602047226180020340346476947011934246835926341409247156083474792158148796695091257848908217577901075797010053065258260358842090757670397444822777604794650499355867490169424359", + "1": "1406580258597117032889664532933358979812139796722692586814415358892098162764552852713043987579442896818681451525105048721570263391940963183111591357037843609111763303521861856746", + "2": "8226691798283916826949050243747165125347294938188267913266542214064983780068693355176098085169894957461451316258618251282503909044606900468377809276758595861570736740946679480676", + "3": "10417526267619447870288116926174026717775573820484630765766424960345632089621094003955538793521774012540070764932235964287060197356269157675198386291765352613560121799625777040665", + }, + r: { + "0": "141114963053587434972182077670397417123797904028000972425879716858953158145961174258743833162544169291275992633915865319312882903279303850311924347853366683751060891300817001897085460055090354761671872800350130546892759320160759972227392448706892214359221145007695427858048535701411964698342178423235618292348262920389517726092934012071061406112747982351226309498144575641147308658800458793389979976931041371788280231819300088641680895436736783007939869689663309188185716841428822808458467333512096940554363268962551749961431781951874502663349694330398950427294330003838430756813298233140375437030815130822858642018020597441680555378752973023021638086941824186677307627233524453472499529379919873626901072811224886885", + "1": "751511177739827888189938952738707647137716958837482416221357690372194162085735786018606817168101269637870682260744453949913244757740914215325768641018320437691088286586337984645473333458900470254020920732463045398879036422195800073514581064869739758408175797039408396823166865075369945769732733117604177322539702101498487569083674778997407335405301917418854173411444171460713208342766251008947214001524824865786116567453463439763485418507284495006292443953426505920044409069622998849534386813034591854364776938228609923045236252210645901159770340783422679704783125300916468150041737492882813020796666361366066418235105952780059379425849298379843547058641263529855994385772159362552860742874516225098475275959568478119", + "2": "798330409215287603425819040593061210710330835689551457127502880575154801140058475440659833469423597825712612478022947896014737139316922024834566217473462004426919123063134593189073289303593113877230152732750593541745230246810513248540730569728549621125340480658186189128351600441684854773196597526635495133319107323259732262020186451319113942531023746525126908666137147173407271841745460647166751359677571720678731118874500549532383284050640570412960217494787025384580459615268894510611681702795470031707017924601414093153368802159108726339960414601335966554436109996891019049646611591224990960048249456858604160139084490639199506963625852569013429617066062982394458606533888339867212607971557936924687675434970940662", + "3": "803931157897200494100151378004910930956098564145028607031188362764108813700886796688779132509609326081465307081977209647793879200711522172139521127668522878944512141316796911309949246894891971971756997547111037640491533568227666386685950885245967483678063361208189685772389137636575825648732597792646299286667904103440651886711611365835678296725966514870619331605230180321784893373570900245083240364635806899488188471893252956526415324717697453439515907349469353899061310746389895096544448092212092478017069187088600896184456162619526005197847774590733725715605941653684129475475845830876475397303848386534949862897792923375246456380030060060753303585915673949779079247503954902534508035381816395473295421601031789206", + DELTA: "1123148876955038782714022883271013042006113912069055423858679191436578762130140592716290652472512266934985923491508239130763693883750836481136954714918663020335261724913808972322623748341483787550202784005511466636595969371357376147537695976872491050438116030639504789506850366983536806218394322683245905916295382289607273248716053348978978264696838670533398699184845231818559510187463958552571214037445657397353512287245163954864510146287456355922032070408671437058866583623110436469362329656644144030991912800446597506625175971603756595583738306449967966080072585596320341317102982125301909952434871907849938050447728834091770189045075147444492347485407391939797245185433711983774538457275974222617445852993764495902", + }, + mj: "8131649707814904014141426751861254140244857332050317458841503042110216419093481757473733625323123641731496276277960922466302979265578851464885510284825995299446748135937916977603", + alpha: "17414834205223304631453928397919867035937960776240824815923661891009625308073198801001756246368167251257244519091657089728510935440907012510605854539948005985438733175243232389873631452022653485420410482696228833160254551469800859029565236385182440844555211274838812856913963865561743794595560084133428492591648474745758594730227151233196021915718994327815831754767073914401251264959967388021520362044130202396040104323744917899345392761829515103849460294150211244999128562418443555840215213846093144110854222193585981495531733734846508962463569386202763518400520203110200425750446361424763050082763231777264368722083956967064739851358410681848981603296317072316332634969025135068640916975948448231086734928658365718057047303118075893003620303759231589188822156056927590467915532990682212392730190959082411349977753577188501685162880216459", + t: { + "0": "6008075905725919393109498075228752704201474968393724984281651932253659767190052065447998311961340721854151833261947291121589506883563013776138177412043120907589992074264181920318787613074087694618880119020352958735598322839735897128055800209280019220239396036142789042748014846462243232009579648816328983474743207241061709696926938171834069546788146564973354423594171938713889978228256049806331041812714767228450607768177297850956191924123068984486888633702798008338751953051205216090025146455497896899925940051849451311521485002472741613575338665706330626779506196107875795814109671229572061836907282434409015592826", + "1": "4309415921252992399071814803637471181034654454405709326676076066374469816691990632177315062992121198314510971710560891065405294077560126086688284846964540294774164133910976391345721011169105324720583494130422278851715198678981198638574293813323499862214915265914201243069364006368241963108300877181796282067228432783412228061289057129488962243474194507516346557029914295576496399732316490107190432222509575032625974093218385715574345236730891626654186618540358152717674333720587231541780225184205225458603870270231091428648008462811626343958112136252365676230076957933041141840226246776458141943341859049844301702188", + "2": "3059868582896507267903954774697168608545014841918131596153765451517489118876512368375733362584762282416784225803448214654626944002380048296823794802960809017952665567792031433340343540914733179989954171859786491703662954943672836629018426409933839298242322909975125397926649926427907936699642799944297719824245004588864851183710691119913104297595283306388040530311173767413959165161572736079217433772928553897093374947474871077507582100511556418531214577977840068630327990917347129256831527366612430478200774656771026964585447320815852568152497232468543944411136151783806303804576195915210229811140899769097302412773", + "3": "4909780361996762726337412735076374851611617588051911973618585541136272814279563894491523673352459587287500273820648944218142737073981849244682720941011684452196676447928671422927390150496692981180535502734732686047675761182563176455177747832930357839012879263966750927903792299563824809937018584869146434871186060658572378662325624041382362497722733387567540989068199269214583977080150201530359325029457274542235546225644626794042540026926297780865635029474338129967746768036882275239799587542932367902031168330346571910764543306011522127059340787822647565791127468080061831307790639175389959038994266885180066587281", + DELTA: "3354544820647651355720276454256945704617727670994673350178064844426037181947030275823257994771689439205040827047853782916554212515767938479152843660630687721765095386427810674166622020483312647473414994585947235941320770960003669860391845728963387261575002246467865873881423713827007389216919874506009130725046237248887473932636572102619016282971112936014026964734438322235747534518783219301594792357544486848636724020316584216575394069441567201434606594606406217029122111708728464278486255647491897215452955687131298174319626297549197767947015118114915088583872727830206281369508371129622414478920487143823483067231", + }, + predicate: { + attr_name: "age", + p_type: "GE", + value: 18, + }, + }, + ], + }, + }, + ], + aggregated_proof: { + c_hash: "49174421319938256583762782015663733712480918038699531953067390414890888248633", + c_list: [ + [ + 13, + 89, + 188, + 183, + 77, + 1, + 198, + 6, + 176, + 8, + 7, + 11, + 148, + 65, + 227, + 96, + 48, + 159, + 104, + 253, + 87, + 195, + 70, + 108, + 31, + 242, + 232, + 27, + 203, + 175, + 196, + 122, + 26, + 57, + 121, + 123, + 114, + 180, + 181, + 139, + 146, + 107, + 236, + 164, + 139, + 190, + 119, + 29, + 254, + 173, + 219, + 219, + 131, + 65, + 253, + 125, + 174, + 81, + 46, + 12, + 197, + 9, + 182, + 106, + 236, + 56, + 204, + 174, + 84, + 98, + 46, + 210, + 64, + 138, + 102, + 217, + 217, + 166, + 62, + 95, + 65, + 55, + 117, + 200, + 196, + 131, + 231, + 162, + 171, + 123, + 33, + 137, + 236, + 179, + 9, + 124, + 59, + 45, + 149, + 12, + 146, + 91, + 87, + 222, + 227, + 242, + 45, + 104, + 225, + 181, + 134, + 81, + 207, + 86, + 8, + 116, + 9, + 239, + 62, + 68, + 242, + 127, + 54, + 213, + 27, + 114, + 40, + 2, + 199, + 160, + 61, + 164, + 64, + 41, + 182, + 8, + 36, + 177, + 245, + 180, + 195, + 246, + 36, + 151, + 254, + 117, + 36, + 238, + 239, + 64, + 186, + 159, + 136, + 171, + 58, + 91, + 64, + 27, + 196, + 236, + 198, + 202, + 198, + 48, + 232, + 43, + 82, + 64, + 221, + 28, + 0, + 173, + 54, + 82, + 90, + 211, + 156, + 115, + 97, + 63, + 39, + 24, + 28, + 34, + 195, + 75, + 243, + 72, + 134, + 111, + 62, + 3, + 150, + 232, + 57, + 167, + 7, + 84, + 18, + 135, + 70, + 25, + 21, + 137, + 243, + 135, + 20, + 218, + 142, + 87, + 36, + 108, + 164, + 56, + 92, + 42, + 134, + 252, + 196, + 18, + 203, + 58, + 207, + 121, + 54, + 194, + 39, + 86, + 109, + 105, + 119, + 30, + 144, + 236, + 146, + 56, + 109, + 121, + 154, + 187, + 87, + 109, + 22, + 132, + 125, + 49, + 53, + 231, + 133, + 163, + 170, + 237, + 95, + 152, + 102, + 182, + ], + [ + 47, + 151, + 215, + 73, + 185, + 173, + 193, + 242, + 196, + 92, + 229, + 237, + 219, + 124, + 15, + 141, + 150, + 0, + 210, + 196, + 83, + 212, + 152, + 95, + 186, + 144, + 209, + 82, + 73, + 246, + 211, + 69, + 1, + 158, + 112, + 5, + 215, + 84, + 79, + 202, + 69, + 156, + 109, + 251, + 131, + 157, + 144, + 119, + 83, + 197, + 218, + 213, + 69, + 245, + 96, + 35, + 175, + 39, + 108, + 228, + 112, + 19, + 115, + 119, + 186, + 39, + 132, + 120, + 95, + 121, + 6, + 148, + 241, + 230, + 157, + 4, + 217, + 199, + 37, + 119, + 134, + 88, + 192, + 242, + 42, + 160, + 221, + 51, + 78, + 7, + 229, + 124, + 46, + 143, + 128, + 28, + 154, + 198, + 190, + 39, + 5, + 114, + 252, + 107, + 11, + 115, + 220, + 141, + 59, + 172, + 52, + 245, + 241, + 117, + 170, + 15, + 157, + 97, + 224, + 25, + 165, + 215, + 1, + 220, + 106, + 97, + 209, + 160, + 102, + 120, + 246, + 213, + 78, + 66, + 183, + 133, + 174, + 98, + 71, + 182, + 217, + 203, + 203, + 107, + 135, + 248, + 84, + 96, + 152, + 219, + 254, + 229, + 244, + 160, + 0, + 212, + 135, + 90, + 30, + 108, + 87, + 218, + 91, + 84, + 206, + 114, + 156, + 84, + 145, + 96, + 51, + 135, + 76, + 57, + 154, + 38, + 53, + 79, + 86, + 100, + 4, + 71, + 50, + 254, + 98, + 109, + 240, + 42, + 129, + 125, + 10, + 236, + 234, + 38, + 1, + 69, + 139, + 126, + 152, + 0, + 230, + 158, + 127, + 237, + 207, + 188, + 167, + 192, + 65, + 184, + 31, + 118, + 31, + 132, + 149, + 209, + 89, + 228, + 51, + 27, + 55, + 121, + 90, + 66, + 138, + 24, + 138, + 151, + 161, + 115, + 123, + 102, + 241, + 40, + 242, + 87, + 234, + 228, + 223, + 84, + 24, + 185, + 78, + 79, + 177, + 153, + 112, + 141, + 59, + 240, + 176, + 141, + 76, + 132, + 199, + 122, + ], + [ + 34, + 35, + 28, + 63, + 9, + 228, + 18, + 190, + 87, + 185, + 203, + 135, + 54, + 213, + 119, + 156, + 228, + 46, + 73, + 117, + 27, + 117, + 94, + 152, + 64, + 104, + 0, + 35, + 11, + 20, + 225, + 203, + 109, + 159, + 164, + 150, + 123, + 33, + 15, + 137, + 97, + 116, + 161, + 70, + 193, + 142, + 201, + 213, + 210, + 100, + 77, + 36, + 5, + 56, + 60, + 229, + 89, + 81, + 204, + 58, + 188, + 195, + 153, + 98, + 225, + 124, + 149, + 108, + 102, + 60, + 219, + 191, + 176, + 227, + 218, + 54, + 58, + 102, + 219, + 255, + 94, + 199, + 225, + 148, + 185, + 100, + 199, + 190, + 93, + 49, + 223, + 114, + 178, + 104, + 226, + 55, + 33, + 22, + 22, + 106, + 99, + 17, + 211, + 141, + 210, + 76, + 33, + 212, + 97, + 190, + 55, + 110, + 38, + 194, + 99, + 99, + 217, + 100, + 249, + 168, + 11, + 204, + 62, + 139, + 54, + 177, + 101, + 75, + 30, + 210, + 252, + 249, + 119, + 58, + 103, + 164, + 125, + 185, + 34, + 112, + 39, + 75, + 151, + 209, + 64, + 34, + 97, + 35, + 33, + 47, + 181, + 96, + 141, + 18, + 198, + 100, + 230, + 237, + 50, + 155, + 12, + 230, + 129, + 250, + 143, + 209, + 170, + 199, + 120, + 240, + 199, + 78, + 242, + 206, + 51, + 230, + 19, + 23, + 227, + 79, + 30, + 239, + 80, + 222, + 252, + 35, + 185, + 89, + 109, + 213, + 144, + 209, + 214, + 111, + 202, + 115, + 81, + 35, + 22, + 234, + 192, + 37, + 149, + 144, + 247, + 146, + 63, + 231, + 250, + 72, + 12, + 110, + 128, + 249, + 163, + 135, + 202, + 176, + 209, + 115, + 115, + 161, + 68, + 4, + 108, + 225, + 187, + 183, + 187, + 117, + 80, + 102, + 206, + 23, + 113, + 244, + 65, + 160, + 146, + 2, + 127, + 106, + 141, + 132, + 68, + 172, + 49, + 205, + 170, + 82, + 216, + 27, + 242, + 137, + 36, + 44, + ], + [ + 24, + 61, + 35, + 215, + 215, + 126, + 169, + 199, + 44, + 44, + 174, + 29, + 54, + 205, + 10, + 4, + 227, + 36, + 31, + 81, + 29, + 241, + 122, + 79, + 195, + 51, + 229, + 47, + 206, + 27, + 143, + 128, + 192, + 244, + 73, + 145, + 173, + 39, + 136, + 136, + 82, + 194, + 73, + 89, + 102, + 155, + 119, + 32, + 188, + 19, + 230, + 123, + 142, + 6, + 108, + 133, + 170, + 194, + 73, + 49, + 89, + 196, + 21, + 74, + 204, + 153, + 11, + 23, + 186, + 244, + 58, + 83, + 69, + 51, + 7, + 167, + 89, + 97, + 26, + 61, + 84, + 243, + 187, + 71, + 201, + 77, + 36, + 104, + 212, + 136, + 106, + 221, + 179, + 204, + 4, + 24, + 45, + 132, + 125, + 178, + 130, + 12, + 55, + 76, + 107, + 56, + 161, + 245, + 237, + 198, + 100, + 29, + 176, + 21, + 209, + 17, + 117, + 16, + 179, + 198, + 133, + 68, + 9, + 240, + 42, + 30, + 86, + 248, + 154, + 195, + 193, + 241, + 13, + 254, + 205, + 214, + 10, + 131, + 137, + 182, + 160, + 188, + 14, + 20, + 204, + 210, + 149, + 6, + 46, + 212, + 142, + 81, + 185, + 114, + 132, + 136, + 141, + 125, + 48, + 194, + 151, + 95, + 195, + 82, + 154, + 91, + 163, + 139, + 216, + 56, + 71, + 224, + 29, + 5, + 228, + 161, + 118, + 198, + 51, + 156, + 99, + 116, + 231, + 5, + 133, + 43, + 58, + 255, + 207, + 252, + 45, + 125, + 50, + 252, + 197, + 198, + 66, + 236, + 26, + 212, + 136, + 29, + 184, + 19, + 3, + 181, + 78, + 75, + 203, + 28, + 96, + 223, + 151, + 7, + 235, + 67, + 138, + 220, + 236, + 205, + 33, + 113, + 61, + 251, + 248, + 16, + 136, + 124, + 108, + 104, + 110, + 162, + 128, + 41, + 40, + 249, + 136, + 132, + 88, + 201, + 222, + 35, + 48, + 92, + 111, + 238, + 31, + 186, + 55, + 128, + 108, + 185, + 245, + 37, + 141, + 229, + ], + [ + 38, + 228, + 152, + 132, + 22, + 197, + 236, + 107, + 242, + 17, + 37, + 44, + 102, + 24, + 143, + 197, + 76, + 250, + 239, + 102, + 218, + 145, + 141, + 157, + 200, + 217, + 226, + 145, + 63, + 135, + 156, + 207, + 199, + 195, + 184, + 5, + 212, + 111, + 91, + 228, + 205, + 195, + 76, + 243, + 46, + 207, + 210, + 247, + 44, + 150, + 235, + 148, + 112, + 176, + 189, + 122, + 151, + 188, + 78, + 32, + 39, + 16, + 226, + 212, + 221, + 64, + 229, + 193, + 52, + 245, + 203, + 73, + 172, + 205, + 167, + 83, + 13, + 78, + 253, + 237, + 136, + 95, + 17, + 70, + 199, + 186, + 111, + 193, + 15, + 139, + 196, + 255, + 123, + 119, + 216, + 215, + 246, + 102, + 254, + 151, + 121, + 20, + 47, + 16, + 144, + 249, + 90, + 72, + 48, + 90, + 35, + 122, + 0, + 12, + 214, + 239, + 152, + 39, + 192, + 35, + 97, + 203, + 135, + 144, + 132, + 48, + 48, + 19, + 40, + 190, + 166, + 165, + 72, + 19, + 10, + 189, + 115, + 108, + 53, + 246, + 227, + 58, + 244, + 192, + 252, + 229, + 102, + 109, + 51, + 143, + 214, + 57, + 118, + 78, + 34, + 206, + 97, + 240, + 141, + 9, + 141, + 221, + 70, + 102, + 91, + 2, + 234, + 243, + 237, + 216, + 28, + 168, + 21, + 255, + 82, + 151, + 16, + 71, + 163, + 233, + 129, + 32, + 122, + 51, + 254, + 190, + 49, + 160, + 21, + 142, + 103, + 181, + 133, + 38, + 33, + 187, + 83, + 243, + 101, + 229, + 223, + 93, + 158, + 63, + 130, + 248, + 236, + 226, + 163, + 110, + 205, + 213, + 140, + 144, + 174, + 90, + 112, + 146, + 239, + 54, + 19, + 106, + 87, + 168, + 197, + 114, + 111, + 121, + 234, + 107, + 210, + 121, + 129, + 196, + 84, + 243, + 69, + 22, + 103, + 246, + 243, + 163, + 5, + 65, + 189, + 131, + 35, + 56, + 199, + 221, + 154, + 73, + 166, + 183, + 98, + 145, + ], + [ + 26, + 146, + 183, + 146, + 240, + 201, + 137, + 76, + 226, + 94, + 104, + 73, + 124, + 17, + 14, + 130, + 107, + 117, + 185, + 14, + 204, + 98, + 168, + 13, + 87, + 203, + 71, + 255, + 23, + 30, + 36, + 151, + 124, + 113, + 203, + 233, + 212, + 63, + 69, + 202, + 122, + 232, + 22, + 128, + 122, + 16, + 127, + 102, + 12, + 206, + 143, + 141, + 74, + 29, + 253, + 44, + 162, + 52, + 225, + 20, + 131, + 94, + 253, + 223, + 69, + 32, + 255, + 1, + 132, + 189, + 177, + 139, + 18, + 159, + 215, + 129, + 112, + 215, + 151, + 34, + 47, + 182, + 154, + 149, + 193, + 197, + 89, + 111, + 239, + 177, + 89, + 107, + 155, + 14, + 183, + 22, + 70, + 109, + 223, + 111, + 158, + 190, + 140, + 3, + 23, + 48, + 26, + 108, + 214, + 22, + 9, + 55, + 178, + 168, + 3, + 98, + 177, + 216, + 228, + 107, + 114, + 225, + 119, + 7, + 232, + 137, + 250, + 110, + 93, + 73, + 36, + 0, + 50, + 77, + 11, + 232, + 161, + 84, + 252, + 5, + 147, + 172, + 83, + 92, + 224, + 154, + 224, + 235, + 199, + 35, + 108, + 223, + 151, + 190, + 39, + 219, + 28, + 132, + 40, + 124, + 234, + 206, + 134, + 190, + 88, + 237, + 16, + 162, + 162, + 35, + 196, + 77, + 164, + 142, + 18, + 63, + 161, + 207, + 64, + 243, + 126, + 247, + 213, + 16, + 41, + 247, + 68, + 185, + 66, + 141, + 199, + 172, + 68, + 127, + 18, + 38, + 104, + 82, + 5, + 90, + 115, + 92, + 117, + 7, + 154, + 229, + 195, + 122, + 179, + 243, + 242, + 249, + 152, + 173, + 133, + 158, + 140, + 228, + 81, + 14, + 38, + 12, + 129, + 131, + 14, + 235, + 239, + 36, + 131, + 226, + 201, + 180, + 116, + 85, + 36, + 90, + 200, + 67, + 65, + 195, + 108, + 203, + 190, + 244, + 255, + 117, + 3, + 232, + 240, + 206, + 224, + 223, + 133, + 91, + 219, + 95, + ], + ], + }, + }, + requested_proof: { + revealed_attrs: { + attr1_referent: { + sub_proof_index: 0, + raw: "test", + encoded: "72155939486846849509759369733266486982821795810448245423168957390607644363272", + }, + }, + self_attested_attrs: { + }, + unrevealed_attrs: { + }, + predicates: { + age: { + sub_proof_index: 0, + }, + }, + }, + identifiers: [ + { + schema_id: "did:web:xyz/resource/schema", + cred_def_id: "did:web:xyz/resource/definition", + }, + ], + }; + + const presentationRequest = { + nonce: "568425184678903953469540", + name: "anoncreds_presentation_request", + version: "0.1", + requested_attributes: { + attr1_referent: { + name: "name", + restrictions: { + cred_def_id: "did:web:xyz/resource/definition" + }, + }, + }, + requested_predicates: { + age: { + name: "age", + p_type: ">=", + p_value: 18, + }, + }, + }; + + test("Should Verify to true when an Anoncreds Presentation submission with all valid attributes and predicates are used", async () => { + const result = await ctx.run(new PresentationVerify({ presentationRequest, presentation, thid })); + + expect(result.pid).toEqual("valid"); + expect(result.data).toEqual(true); + }); + + test("Should Verify to false when an Anoncreds Presentation submission with invalid nonce", async () => { + const testRequest = { ...presentationRequest }; + testRequest.nonce = "832231068342146277491801"; + + const result = await ctx.run(new PresentationVerify({ presentationRequest: testRequest, presentation, thid })); + + expect(result.pid).toEqual("valid"); + expect(result.data).toEqual(false); + }); + + test("Should throw when presentation invalid", async () => { + const testPresentation = {} as any; + const sut = ctx.run(new PresentationVerify({ presentationRequest, presentation: testPresentation, thid })); + + expect(sut).rejects.toThrowError(); + }); + + test("no presentationRequest - throws", async () => { + const sut = ctx.run(new PresentationVerify({ presentation, thid })); + + expect(sut).rejects.toThrowError(); + }); + + test("presentationRequest message in Pluto - runs as expected", async () => { + const attach = new AttachmentDescriptor({ + json: { + nonce: "568425184678903953469540", + name: "anoncreds_presentation_request", + version: "0.1", + requested_attributes: { + attr1_referent: { + name: "name", + restrictions: { + cred_def_id: "did:web:xyz/resource/definition" + }, + }, + }, + requested_predicates: { + age: { + name: "age", + p_type: ">=", + p_value: 18, + }, + }, + }, + }); + const msg = new RequestPresentation({} as any, [attach], Fixtures.DIDs.peerDID1, Fixtures.DIDs.peerDID2, thid); + vi.spyOn(pluto, "getAllMessages").mockResolvedValue([msg.makeMessage()]); + + const result = await ctx.run(new PresentationVerify({ presentation, thid })); + + expect(result.pid).toEqual("valid"); + expect(result.data).toEqual(true); + }); + + test("presentationRequest message in Pluto - no attachment - throws", async () => { + const msg = new RequestPresentation({} as any, [], Fixtures.DIDs.peerDID1, Fixtures.DIDs.peerDID2, thid); + vi.spyOn(pluto, "getAllMessages").mockResolvedValue([msg.makeMessage()]); + + const sut = ctx.run(new PresentationVerify({ presentation, thid })); + + expect(sut).rejects.toThrowError(); + }); + + test("no message in Pluto - throws", async () => { + vi.spyOn(pluto, "getAllMessages").mockResolvedValue([]); + + const sut = ctx.run(new PresentationVerify({ presentation, thid })); + + expect(sut).rejects.toThrowError(); + }); + }); +}); diff --git a/tests/plugins/dif/CreatePresentationDefinition.test.ts b/tests/plugins/dif/CreatePresentationDefinition.test.ts new file mode 100644 index 000000000..8c444986a --- /dev/null +++ b/tests/plugins/dif/CreatePresentationDefinition.test.ts @@ -0,0 +1,46 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { Task } from '../../../src/utils'; +import { Apollo, Castor } from '../../../src'; +import { JWT, SDJWT } from "../../../src/pollux/utils/jwt"; +import { CreatePresentationDefinition } from '../../../src/plugins/internal/dif/module/CreatePresentationDefinition'; + +describe("Plugins - DIF", () => { + let ctx: Task.Context; + + beforeEach(() => { + const apollo = new Apollo(); + const castor = new Castor(apollo); + ctx = Task.Context.make({ + Apollo: apollo, + Castor: castor, + JWT: new JWT(), + SDJWT: new SDJWT(), + }); + }); + + describe("CreatePresentationDefinition", () => { + test("Should be able to create a presentationDefinitionRequest for a JWT Credential", async () => { + const result = await ctx.run(new CreatePresentationDefinition({ + issuer: "did:prism:12345", + claims: { + name: { + type: 'string', + pattern: 'identus' + } + } + })); + + const presentation_definition = result.data.presentation_definition; + expect(presentation_definition).haveOwnProperty("id"); + expect(presentation_definition).haveOwnProperty("format"); + expect(presentation_definition).haveOwnProperty("input_descriptors"); + expect(presentation_definition.format).haveOwnProperty("jwt"); + expect(Array.isArray(presentation_definition.input_descriptors)).to.eq(true); + expect(presentation_definition.input_descriptors.length).to.eq(1); + expect(presentation_definition.input_descriptors.at(0)).haveOwnProperty('constraints'); + expect(presentation_definition.input_descriptors.at(0)?.constraints.fields.length).to.eq(2); + expect(presentation_definition.input_descriptors.at(0)?.constraints.fields[0].path.at(0)).to.eq('$.vc.credentialSubject.name'); + expect(presentation_definition.input_descriptors.at(0)?.constraints.fields[1].path.at(0)).to.eq('$.vc.issuer'); + }); + }); +}); diff --git a/tests/plugins/dif/IsCredentialRevoked.test.ts b/tests/plugins/dif/IsCredentialRevoked.test.ts new file mode 100644 index 000000000..6bca1b504 --- /dev/null +++ b/tests/plugins/dif/IsCredentialRevoked.test.ts @@ -0,0 +1,530 @@ +import { vi, describe, it, beforeEach, expect } from 'vitest'; +import { base64 } from "multiformats/bases/base64"; +import { Task } from '../../../src/utils'; +import * as Fixtures from "../../fixtures"; +import { Apollo, JWTCredential, JWT_VC_PROPS, SDJWTCredential } from '../../../src'; +import { IsCredentialRevoked } from '../../../src/plugins/internal/dif/IsCredentialRevoked'; +import { Api, ApiResponse, Curve, KeyTypes } from '../../../src/domain'; +import { VerificationKeyType } from '../../../src/castor/types'; + + +describe("Plugins - DIF", () => { + describe("IsCredentialRevoked", () => { + let ctx: Task.Context; + let api: Api; + let apollo: Apollo; + const revocableJWTCredential = `eyJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6cHJpc206YmM5ZGFhZWFmMGFkNjczZjVkNTViM2I2NjEyYTE2NTNiYzcyYWMxNjU5Y2VmYTgxYzZlZWY0NWMxZjcyMTYzOTpDcmtCQ3JZQkVqb0tCbUYxZEdndE1SQUVTaTRLQ1hObFkzQXlOVFpyTVJJaEFqRDNnM3ctcHNnRXZQcUJxUDJmVjhPQXAwQ0l3WjVYU3FhMU9OWU1HOGRQRWpzS0IybHpjM1ZsTFRFUUFrb3VDZ2x6WldOd01qVTJhekVTSVFQRGNPbm9BV25YODBhZnA2aVVEZUl6ZUViMXMySFVPUEo5TEpRRTd1RzdYeEk3Q2dkdFlYTjBaWEl3RUFGS0xnb0pjMlZqY0RJMU5tc3hFaUVDc3luYTRsbkw3anhfSnctTXUtUjd3UUppSnhCNGpnMWUwODN1Q252amNhSSIsInN1YiI6ImRpZDpwcmlzbTozZjBiNDQ5NjI3NmI3NGEzMTU3ZmRiOTEwODU5MDExYjhjZWQwNjU1ZGYyNWU3ZjgwNTAyZjE0OGU2NmM1NGU4OkN0OEJDdHdCRW5RS0gyRjFkR2hsYm5ScFkyRjBhVzl1WVhWMGFHVnVkR2xqWVhScGIyNUxaWGtRQkVKUENnbHpaV053TWpVMmF6RVNJS0ZpZjRlcnNMOFF2SFF2VmxXUEFNaHFPNmwzbXZSbUp5ZlRFRTYzZzI2MEdpRG9PNS1KRzR3Z1JkZk1LcXlqZnp2ek9sSXRsNDNsdDQ0Z21TMWxtaFpKZUJKa0NnOXRZWE4wWlhKdFlYTjBaWEpMWlhrUUFVSlBDZ2x6WldOd01qVTJhekVTSUtGaWY0ZXJzTDhRdkhRdlZsV1BBTWhxTzZsM212Um1KeWZURUU2M2cyNjBHaURvTzUtSkc0d2dSZGZNS3F5amZ6dnpPbEl0bDQzbHQ0NGdtUzFsbWhaSmVBIiwibmJmIjoxNzE1MDA2OTY4LCJ2YyI6eyJjcmVkZW50aWFsU3ViamVjdCI6eyJlbWFpbEFkZHJlc3MiOiJjb3Jwb3JhdGVAZG9tYWluLmNvbSIsImRyaXZpbmdDbGFzcyI6MSwiZHJpdmluZ0xpY2Vuc2VJRCI6IkVTLTEyMzQ1Njc4OTAiLCJpZCI6ImRpZDpwcmlzbTozZjBiNDQ5NjI3NmI3NGEzMTU3ZmRiOTEwODU5MDExYjhjZWQwNjU1ZGYyNWU3ZjgwNTAyZjE0OGU2NmM1NGU4OkN0OEJDdHdCRW5RS0gyRjFkR2hsYm5ScFkyRjBhVzl1WVhWMGFHVnVkR2xqWVhScGIyNUxaWGtRQkVKUENnbHpaV053TWpVMmF6RVNJS0ZpZjRlcnNMOFF2SFF2VmxXUEFNaHFPNmwzbXZSbUp5ZlRFRTYzZzI2MEdpRG9PNS1KRzR3Z1JkZk1LcXlqZnp2ek9sSXRsNDNsdDQ0Z21TMWxtaFpKZUJKa0NnOXRZWE4wWlhKdFlYTjBaWEpMWlhrUUFVSlBDZ2x6WldOd01qVTJhekVTSUtGaWY0ZXJzTDhRdkhRdlZsV1BBTWhxTzZsM212Um1KeWZURUU2M2cyNjBHaURvTzUtSkc0d2dSZGZNS3F5amZ6dnpPbEl0bDQzbHQ0NGdtUzFsbWhaSmVBIiwiZGF0ZU9mSXNzdWFuY2UiOiIyMDIzLTAxLTAxVDAyOjAyOjAyWiJ9LCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sIkBjb250ZXh0IjpbImh0dHBzOlwvXC93d3cudzMub3JnXC8yMDE4XC9jcmVkZW50aWFsc1wvdjEiXSwiY3JlZGVudGlhbFN0YXR1cyI6eyJzdGF0dXNQdXJwb3NlIjoiUmV2b2NhdGlvbiIsInN0YXR1c0xpc3RJbmRleCI6MSwiaWQiOiJodHRwOlwvXC8xOTIuMTY4LjE1NC4yMDU6ODAwMFwvcHJpc20tYWdlbnRcL2NyZWRlbnRpYWwtc3RhdHVzXC8xYzE1Yjk2My1kYzRkLTQ3NjUtYjc1Mi01M2EzZmQxZjE4MzMjMSIsInR5cGUiOiJTdGF0dXNMaXN0MjAyMUVudHJ5Iiwic3RhdHVzTGlzdENyZWRlbnRpYWwiOiJodHRwOlwvXC8xOTIuMTY4LjE1NC4yMDU6ODAwMFwvcHJpc20tYWdlbnRcL2NyZWRlbnRpYWwtc3RhdHVzXC8xYzE1Yjk2My1kYzRkLTQ3NjUtYjc1Mi01M2EzZmQxZjE4MzMifX19.NxuJoiEgSnGs7suM5cxDq3tZ6ZYVDAscnKBuAXghW0KD9MhSr1vBUo9F6y0YkjhHBY4Y_gTGnIMBwgLYjcNVKw`; + + beforeEach(() => { + api = { + request: async () => new ApiResponse(new Uint8Array(), 200), + }; + apollo = new Apollo(); + ctx = Task.Context.make({ + Api: api, + Apollo: apollo, + }); + }); + + + it("Should correctly determine that a Credential is revoked when calling the credentialStatus [EcdsaSecp256k1Signature2019] list endpoints", async () => { + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "data:application/json;base64,eyJAY29udGV4dCI6WyJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L3YxIl0sInR5cGUiOiJFY2RzYVNlY3AyNTZrMVZlcmlmaWNhdGlvbktleTIwMTkiLCJwdWJsaWNLZXlKd2siOnsiY3J2Ijoic2VjcDI1NmsxIiwia2V5X29wcyI6WyJ2ZXJpZnkiXSwia3R5IjoiRUMiLCJ4IjoiQ1hJRmwyUjE4YW1lTEQteWtTT0dLUW9DQlZiRk01b3Vsa2MydklySnRTND0iLCJ5IjoiRDJRWU5pNi1BOXoxbHhwUmpLYm9jS1NUdk5BSXNOVnNsQmpsemVnWXlVQT0ifX0=", + "created": "2024-07-25T22:49:59.091957Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..FJLUBsZhGB1o_G1UwsVaoL-8agvcpoelJtAr2GlNOOqCSOd-WNEj5-FOgv0m0QcdKMokl2TxibJMg3Y-MJq4-A" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/01def9a2-2bcb-4bb3-8a36-6834066431d0", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1721947798, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BIQEAAAACIKf6f4UzLEADAAAAAAAAAAAAAAAAAAAAvA3PduITAEAAAA==" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const credential2 = JWTCredential.fromJWS(revocableJWTCredential); + const credential3 = JWTCredential.fromJWS(revocableJWTCredential); + + const vc = credential.properties.get(JWT_VC_PROPS.vc); + vc.credentialStatus.statusListIndex = 1; + credential.properties.set(JWT_VC_PROPS.vc, vc); + + const vc2 = credential2.properties.get(JWT_VC_PROPS.vc); + vc2.credentialStatus.statusListIndex = 2; + credential2.properties.set(JWT_VC_PROPS.vc, vc2); + + const vc3 = credential3.properties.get(JWT_VC_PROPS.vc); + vc3.credentialStatus.statusListIndex = 3; + credential3.properties.set(JWT_VC_PROPS.vc, vc3); + + + const revoked = await ctx.run(new IsCredentialRevoked({ credential })); + const revoked2 = await ctx.run(new IsCredentialRevoked({ credential: credential2 })); + const revoked3 = await ctx.run(new IsCredentialRevoked({ credential: credential3 })); + + expect(revoked.data).to.eq(true); + expect(revoked2.data).to.eq(true); + expect(revoked3.data).to.eq(false); + + }); + + it("Should throw an error if we try to use a SDJWT credential ", async () => { + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "data:application/json;base64,eyJAY29udGV4dCI6WyJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L3YxIl0sInR5cGUiOiJFY2RzYVNlY3AyNTZrMVZlcmlmaWNhdGlvbktleTIwMTkiLCJwdWJsaWNLZXlKd2siOnsiY3J2Ijoic2VjcDI1NmsxIiwia2V5X29wcyI6WyJ2ZXJpZnkiXSwia3R5IjoiRUMiLCJ4IjoiVFlCZ21sM1RpUWRSX1lRRDFoSXVOTzhiUnluU0otcmxQcWFVd3JXa3EtRT0iLCJ5IjoiVjBnVFlBM0xhbFd3Q3hPZHlqb2ZoR2JkYVFEd3EwQXdCblNodFJLXzNYZz0ifX0=", + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2024", + "statusPurpose": "Revocation", + //Credential index [0] has a value of 2 + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + const credential = SDJWTCredential.fromJWS(Fixtures.Credentials.SDJWT.credentialPayloadEncoded); + const sut = ctx.run(new IsCredentialRevoked({ credential: credential as any })); + + expect(sut).rejects.toThrowError(`Only JWT Credential are supported`); + }); + //* + + it("Should throw an error if we try to use a credential which an unsupported type ", async () => { + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "data:application/json;base64,eyJAY29udGV4dCI6WyJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L3YxIl0sInR5cGUiOiJFY2RzYVNlY3AyNTZrMVZlcmlmaWNhdGlvbktleTIwMTkiLCJwdWJsaWNLZXlKd2siOnsiY3J2Ijoic2VjcDI1NmsxIiwia2V5X29wcyI6WyJ2ZXJpZnkiXSwia3R5IjoiRUMiLCJ4IjoiVFlCZ21sM1RpUWRSX1lRRDFoSXVOTzhiUnluU0otcmxQcWFVd3JXa3EtRT0iLCJ5IjoiVjBnVFlBM0xhbFd3Q3hPZHlqb2ZoR2JkYVFEd3EwQXdCblNodFJLXzNYZz0ifX0=", + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2024", + "statusPurpose": "Revocation", + //Credential index [0] has a value of 2 + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const vc = credential.properties.get(JWT_VC_PROPS.vc); + vc.credentialStatus.statusListIndex = 0; + vc.credentialStatus.type = "Wrong"; + credential.properties.set(JWT_VC_PROPS.vc, vc); + + const sut = ctx.run(new IsCredentialRevoked({ credential: credential as any })); + + expect(sut).rejects.toThrowError(`CredentialStatus revocation type not supported`); + }); + + it("Should throw an error if the credential status proof contains an invalid verificationMethod", async () => { + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "data:application/json;base64", + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError(`CredentialStatus proof invalid verificationMethod`); + + }); + + it("should throw an error if no jwk is provided", async () => { + const encoded = base64.baseEncode(Buffer.from(JSON.stringify({ noJWK: true }))); + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": `data:application/json;base64,${encoded}`, + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError("No public jwk provided"); + }); + + it("should throw an eror if a wrong verificationMethod type is used", async () => { + const encoded2 = base64.baseEncode(Buffer.from(JSON.stringify({ publicKeyJwk: { x: "423", y: "123" } }))); + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": `data:application/json;base64,${encoded2}`, + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError("Err Only EcdsaSecp256k1VerificationKey2019 is supported"); + }); + + it("Should throw an error if a wrong key type is used", async () => { + const encoded3 = base64.baseEncode(Buffer.from(JSON.stringify({ type: VerificationKeyType.EcdsaSecp256k1VerificationKey2019, publicKeyJwk: { x: "423", y: "123" } }))); + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": `data:application/json;base64,${encoded3}`, + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError("Err Invalid JWK kty: undefined, should be EC"); + + }); + + it("Should throw an error if a wrong key curve is used", async () => { + const encoded4 = base64.baseEncode(Buffer.from(JSON.stringify({ type: VerificationKeyType.EcdsaSecp256k1VerificationKey2019, publicKeyJwk: { x: "423", y: "123", kty: KeyTypes.EC } }))); + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": `data:application/json;base64,${encoded4}`, + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError("Err Invalid JWK crv: undefined, should be secp256k1"); + }); + + it("Should throw an eror if an invalid verificationKey is used", async () => { + const encoded5 = base64.baseEncode(Buffer.from(JSON.stringify({ type: VerificationKeyType.EcdsaSecp256k1VerificationKey2019, publicKeyJwk: { x: 'TYBgml3TiQdR_YQD1hIuNO8bRynSJ-rlPqaUwrWkq-E=', y: 'V0gTYA3LalWwCxOdyjofhGbdaQDwq0AwBnShtRK_3Xg=', kty: KeyTypes.EC, crv: Curve.SECP256K1.toLocaleLowerCase() } }))); + + // return privateKey so .canVerify() returns false + vi.spyOn(apollo, "createPublicKey").mockReturnValue(Fixtures.Keys.ed25519.privateKey); + + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": `data:application/json;base64,${encoded5}`, + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError("CredentialStatus proof invalid verifying key"); + }); + + it("Should throw an error if the status jwt is invalid", async () => { + const encoded6 = base64.baseEncode(Buffer.from(JSON.stringify({ type: VerificationKeyType.EcdsaSecp256k1VerificationKey2019, publicKeyJwk: { x: 'TYBgml3TiQdR_YQD1hIuNO8bRynSJ-rlPqaUwrWkq-E=', y: 'V0gTYA3LalWwCxOdyjofhGbdaQDwq0AwBnShtRK_3Xg=', kty: KeyTypes.EC, crv: Curve.SECP256K1.toLocaleLowerCase() } }))); + + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": `data:application/json;base64,${encoded6}`, + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ." + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError("Credential status jwt is invalid"); + }); + + it("should throw an error if wrong signature is used", async () => { + const encoded8 = base64.baseEncode( + Buffer.from( + JSON.stringify({ + type: VerificationKeyType.EcdsaSecp256k1VerificationKey2019, + publicKeyJwk: { + x: 'TYBgml3TiQdR_YQD1hIuNO8bRynSJ-rlPqaUwrWkq-E=', + y: 'V0gTYA3LalWwCxOdyjofhGbdaQDwq0AwBnShtRK_3Xg=', + kty: KeyTypes.EC, + crv: Curve.SECP256K1.toLocaleLowerCase() + } + } + ) + ) + ); + + vi.spyOn(apollo, "createPublicKey").mockReturnValue(Fixtures.Keys.ed25519.publicKey); + + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": `data:application/json;base64,${encoded8}`, + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError("CredentialStatus invalid signature"); + }); + + /* + it("Should throw an error if we cannot decode the revocation registry correctly ", async () => { + + + (pollux as any)._pako = { + ungzip: vi.fn(() => { + throw new Error('whatever'); + }), + }; + + const fetchRevocationRegistryStub = vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2019", + "proofPurpose": "assertionMethod", + "verificationMethod": "data:application/json;base64,eyJAY29udGV4dCI6WyJodHRwczovL3czaWQub3JnL3NlY3VyaXR5L3YxIl0sInR5cGUiOiJFY2RzYVNlY3AyNTZrMVZlcmlmaWNhdGlvbktleTIwMTkiLCJwdWJsaWNLZXlKd2siOnsiY3J2Ijoic2VjcDI1NmsxIiwia2V5X29wcyI6WyJ2ZXJpZnkiXSwia3R5IjoiRUMiLCJ4IjoiVFlCZ21sM1RpUWRSX1lRRDFoSXVOTzhiUnluU0otcmxQcWFVd3JXa3EtRT0iLCJ5IjoiVjBnVFlBM0xhbFd3Q3hPZHlqb2ZoR2JkYVFEd3EwQXdCblNodFJLXzNYZz0ifX0=", + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ..Q1mj3jMf5DWK83E55r6vNUPpsYYgclgwYoNFBSYBzA5x6fI_2cPHJsXECnQlG1XMj2ifldngpJXegTpwe3Fgwg" + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + await expect(pollux.isCredentialRevoked(JWTCredential.fromJWS(revocableJWTCredential))) + .rejects.toThrowError(`Couldn't ungzip base64 encoded list, err: whatever`); + + fetchRevocationRegistryStub.mockRestore(); + }); + //*/ + it("Should throw an error if the status proof type is invalid", async () => { + const encoded6 = base64.baseEncode(Buffer.from(JSON.stringify({ + type: VerificationKeyType.EcdsaSecp256k1VerificationKey2019, + publicKeyJwk: { x: 'TYBgml3TiQdR_YQD1hIuNO8bRynSJ-rlPqaUwrWkq-E=', y: 'V0gTYA3LalWwCxOdyjofhGbdaQDwq0AwBnShtRK_3Xg=', kty: KeyTypes.EC, crv: Curve.SECP256K1.toLocaleLowerCase() } + }))); + vi.spyOn(api, "request").mockResolvedValue(new ApiResponse({ + "proof": { + "type": "EcdsaSecp256k1Signature2024", + "proofPurpose": "assertionMethod", + "verificationMethod": `data:application/json;base64,${encoded6}`, + "created": "2024-06-14T10:56:59.948091Z", + "jws": "eyJiNjQiOmZhbHNlLCJjcml0IjpbImI2NCJdLCJhbGciOiJFUzI1NksifQ." + }, + "@context": [ + "https://www.w3.org/2018/credentials/v1", + "https://w3id.org/vc/status-list/2021/v1" + ], + "type": [ + "VerifiableCredential", + "StatusList2021Credential" + ], + "id": "http://localhost:8085/credential-status/575092c2-7eb0-40ae-8f41-3b499f45f3dc", + "issuer": "did:prism:462c4811bf61d7de25b3baf86c5d2f0609b4debe53792d297bf612269bf8593a", + "issuanceDate": 1717714047, + "credentialSubject": { + "type": "StatusList2021", + "statusPurpose": "Revocation", + "encodedList": "H4sIAAAAAAAA_-3BMQ0AAAACIGf_0MbwARoAAAAAAAAAAAAAAAAAAADgbbmHB0sAQAAA" + } + }, 200)); + + const credential = JWTCredential.fromJWS(revocableJWTCredential); + const sut = ctx.run(new IsCredentialRevoked({ credential })); + + await expect(sut).rejects.toThrowError("CredentialStatus proof type not supported"); + }); + //*/ + }); +}); diff --git a/tests/plugins/dif/PresentationRequest.test.ts b/tests/plugins/dif/PresentationRequest.test.ts new file mode 100644 index 000000000..1bf7b0b0f --- /dev/null +++ b/tests/plugins/dif/PresentationRequest.test.ts @@ -0,0 +1,156 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { PresentationRequest } from '../../../src/plugins/internal/dif/PresentationRequest'; +import { DIF } from '../../../src/plugins/internal/dif/types'; +import { Task } from '../../../src/utils'; +import { Apollo, Castor, JWTCredential, SDJWTCredential } from '../../../src'; +import { JWT, SDJWT } from "../../../src/pollux/utils/jwt"; +import * as Fixtures from "../../fixtures"; +import { mockPluto } from "../../fixtures/inmemory/factory"; + +describe("Plugins - DIF", () => { + let ctx: Task.Context; + + beforeEach(() => { + const apollo = new Apollo(); + const castor = new Castor(apollo); + const pluto = mockPluto(); + ctx = Task.Context.make({ + Apollo: apollo, + Castor: castor, + Pluto: pluto, + JWT: new JWT(), + SDJWT: new SDJWT(), + }); + + vi.spyOn(pluto, "getDIDPrivateKeysByDID").mockResolvedValue([ + Fixtures.Keys.secp256K1.privateKey + ]); + }); + + describe("PresentationRequest", () => { + describe("JWT", () => { + const credential = JWTCredential.fromJWS(Fixtures.Credentials.JWT.credentialPayloadEncoded); + const presentationRequest: DIF.Presentation.Request = { + // options: { + // challenge: "11c91493-01b3-4c4d-ac36-b336bab5bddf", + // domain: "http://localhost:8000/prism-agent" + // }, + presentation_definition: { + id: "b2a49475-f8ba-4952-a719-a28e909858fa", + format: {}, + input_descriptors: [], + } + }; + + test("returns Payload", async () => { + const sut = new PresentationRequest({ credential, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.pid).toBe(DIF.PRESENTATION); + expect(result.data).toEqual(expect.objectContaining({})); + }); + + test("Payload.data is EmbedTarget", async () => { + const sut = new PresentationRequest({ credential, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.data).toEqual({ + verifiablePresentation: expect.arrayContaining([expect.stringMatching("")]), + presentation_submission: { + id: expect.stringMatching(""), + definition_id: presentationRequest.presentation_definition.id, + descriptor_map: [], + }, + }); + }); + + test("presentation_definition.input_descriptors map to descriptor_map", async () => { + const inputDescriptor: DIF.Presentation.Definition.InputDescriptor = { + id: "abc123", + constraints: { fields: [], limit_disclosure: "preferred" } + }; + const presentationRequest: DIF.Presentation.Request = { + presentation_definition: { + id: "b2a49475-f8ba-4952-a719-a28e909858fa", + format: {}, + input_descriptors: [inputDescriptor], + } + }; + + const sut = new PresentationRequest({ credential, presentationRequest }); + const result = await ctx.run(sut); + + const descriptorMap = result.data.presentation_submission.descriptor_map; + expect(descriptorMap).toHaveLength(1); + expect(descriptorMap[0]).toEqual({ + id: inputDescriptor.id, + format: "jwt_vp", + path: "$.verifiablePresentation[0]", + path_nested: { + id: inputDescriptor.id, + format: 'jwt_vc', + path: "$.vp.verifiableCredential[0]", + } + }); + }); + }); + }); + + describe("SDJWT", () => { + const credential = SDJWTCredential.fromJWS(Fixtures.Credentials.JWT.credentialPayloadEncoded); + const presentationRequest: DIF.Presentation.Request = { + presentation_definition: { + id: "b2a49475-f8ba-4952-a719-a28e909858fa", + format: {}, + input_descriptors: [], + } + }; + + test("returns Payload", async () => { + const sut = new PresentationRequest({ credential, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.pid).toBe(DIF.PRESENTATION); + expect(result.data).toEqual(expect.objectContaining({})); + }); + + test("Payload.data is EmbedTarget", async () => { + const sut = new PresentationRequest({ credential, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.data).toEqual({ + verifiablePresentation: expect.arrayContaining([expect.stringMatching("")]), + presentation_submission: { + id: expect.stringMatching(""), + definition_id: presentationRequest.presentation_definition.id, + descriptor_map: [], + }, + }); + }); + + test("presentation_definition.input_descriptors map to descriptor_map", async () => { + const inputDescriptor: DIF.Presentation.Definition.InputDescriptor = { + id: "abc123", + constraints: { fields: [], limit_disclosure: "preferred" } + }; + const presentationRequest: DIF.Presentation.Request = { + presentation_definition: { + id: "b2a49475-f8ba-4952-a719-a28e909858fa", + format: {}, + input_descriptors: [inputDescriptor], + } + }; + + const sut = new PresentationRequest({ credential, presentationRequest }); + const result = await ctx.run(sut); + + const descriptorMap = result.data.presentation_submission.descriptor_map; + expect(descriptorMap).toHaveLength(1); + expect(descriptorMap[0]).toEqual({ + id: inputDescriptor.id, + format: "sdjwt", + path: "$.verifiablePresentation[0]", + }); + }); + }); +}); diff --git a/tests/plugins/dif/PresentationVerify.test.ts b/tests/plugins/dif/PresentationVerify.test.ts new file mode 100644 index 000000000..13e51e00c --- /dev/null +++ b/tests/plugins/dif/PresentationVerify.test.ts @@ -0,0 +1,271 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { DIF } from '../../../src/plugins/internal/dif/types'; +import { Task } from '../../../src/utils'; +import { Apollo, Castor } from '../../../src'; +import { JWT, SDJWT } from "../../../src/pollux/utils/jwt"; +import { PresentationVerify } from '../../../src/plugins/internal/dif/PresentationVerify'; + +describe("Plugins - DIF", () => { + let ctx: Task.Context; + + beforeEach(() => { + const apollo = new Apollo(); + const castor = new Castor(apollo); + ctx = Task.Context.make({ + Apollo: apollo, + Castor: castor, + JWT: new JWT(), + SDJWT: new SDJWT(), + }); + }); + + describe("PresentationVerify", () => { + describe("JWT", () => { + const issuerDID = "did:prism:0858c30daf6d0cc3e0b1ae31b7cb212f4446a6e0f47a5926b3ba7dc64986157b:CmEKXxJdCghtYXN0ZXItMBABQk8KCXNlY3AyNTZrMRIgM3HxlER-HNHG59NAGoJJ7OdA5XlQAbOU5JqPGmnkZiQaIFYj9QUSo_xiemYiLHlBBkwHjZZKR1FjSA2OlgKI9iC9"; + const issuerPK = { + curve: "Secp256k1", + hex: "ad29fa414bdc84737fba87743e7b9f876e278a18d0036ab782042e0c6e7a4672" + }; + const holderDID = "did:prism:38e78e4dd9711c49a1fb533b0247c5a26516e38e99a259cd21ff5b84097533d6:CmEKXxJdCghtYXN0ZXItMBABQk8KCXNlY3AyNTZrMRIgp6Bi0cCJRnOQQxeL5RXjW7v1zKJrJQ7Wcoxy5OGV3HkaID3gsu86_sFD09sWeR7O8aQCVeludTW5aYSPoPiQEgBi"; + const holderPK = { + curve: "Secp256k1", + hex: "e7e232848313a57c5161a9d6e1813db133ae9f1baffb3c5e82dba220746bf18a" + }; + const jwt = "eyJhbGciOiJFUzI1NksiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MzY1MTEzNzcsImV4cCI6MTczOTE4OTc3NzYyNiwiaXNzIjoiZGlkOnByaXNtOjA4NThjMzBkYWY2ZDBjYzNlMGIxYWUzMWI3Y2IyMTJmNDQ0NmE2ZTBmNDdhNTkyNmIzYmE3ZGM2NDk4NjE1N2I6Q21FS1h4SmRDZ2h0WVhOMFpYSXRNQkFCUWs4S0NYTmxZM0F5TlRack1SSWdNM0h4bEVSLUhOSEc1OU5BR29KSjdPZEE1WGxRQWJPVTVKcVBHbW5rWmlRYUlGWWo5UVVTb194aWVtWWlMSGxCQmt3SGpaWktSMUZqU0EyT2xnS0k5aUM5IiwibmJmIjoxNzM2NTExMzc3NjI2LCJzdWIiOiJkaWQ6cHJpc206MzhlNzhlNGRkOTcxMWM0OWExZmI1MzNiMDI0N2M1YTI2NTE2ZTM4ZTk5YTI1OWNkMjFmZjViODQwOTc1MzNkNjpDbUVLWHhKZENnaHRZWE4wWlhJdE1CQUJRazhLQ1hObFkzQXlOVFpyTVJJZ3A2QmkwY0NKUm5PUVF4ZUw1UlhqVzd2MXpLSnJKUTdXY294eTVPR1YzSGthSUQzZ3N1ODZfc0ZEMDlzV2VSN084YVFDVmVsdWRUVzVhWVNQb1BpUUVnQmkiLCJ2YyI6eyJAY29udGV4dCI6WyJodHRwczovL3d3dy53My5vcmcvMjAxOC9jcmVkZW50aWFscy92MSJdLCJ0eXBlIjpbIlZlcmlmaWFibGVDcmVkZW50aWFsIl0sImlzc3VlciI6ImRpZDpwcmlzbTowODU4YzMwZGFmNmQwY2MzZTBiMWFlMzFiN2NiMjEyZjQ0NDZhNmUwZjQ3YTU5MjZiM2JhN2RjNjQ5ODYxNTdiOkNtRUtYeEpkQ2dodFlYTjBaWEl0TUJBQlFrOEtDWE5sWTNBeU5UWnJNUklnTTNIeGxFUi1ITkhHNTlOQUdvSko3T2RBNVhsUUFiT1U1SnFQR21ua1ppUWFJRllqOVFVU29feGllbVlpTEhsQkJrd0hqWlpLUjFGalNBMk9sZ0tJOWlDOSIsImlzc3VhbmNlRGF0ZSI6IjIwMjUtMDEtMTBUMTI6MTY6MTcuNjI2WiIsImNyZWRlbnRpYWxTdWJqZWN0Ijp7ImNvdXJzZSI6IklkZW50dXMgVHJhaW5pbmcgY291cnNlIENlcnRpZmljYXRpb24gMjAyNCJ9fX0.SGpkR7YG-yYyNxZ4w_UF1yueu2gf73R-BktKX_mPZth2s4pOdr5NFuz3M6faFBZKTbD5ydGHFE4bbWLk09vB2g"; + const payload = { + iss: issuerDID, + nbf: 1736511377626, + exp: 1739189777626, + sub: holderDID, + vc: { + "@context": ["https://www.w3.org/2018/credentials/v1"], + type: ["VerifiableCredential"], + issuer: issuerDID, + issuanceDate: "2025-01-10T12:16:17.626Z", + credentialSubject: { + course: "Identus Training course Certification 2024", + }, + }, + }; + + const presentation: DIF.EmbedTarget = { + presentation_submission: { + id: "0b17b996-7d52-4b2a-81fd-e6ffcd6dd188", + definition_id: "ba7814d4-4bdf-42a5-a3f9-a2e86419e6c4", + descriptor_map: [{ + id: "9e50eb6b-e7fc-46a8-bd91-7d53ac4adc53", + format: "jwt_vp", + path: "$.verifiablePresentation[0]", + path_nested: { + id: "9e50eb6b-e7fc-46a8-bd91-7d53ac4adc53", + format: "jwt_vc", + path: "$.vp.verifiableCredential[0]", + }, + }, + ], + }, + verifiablePresentation: [ + "eyJhbGciOiJFUzI1NksiLCJraWQiOiJkaWQ6cHJpc206MzhlNzhlNGRkOTcxMWM0OWExZmI1MzNiMDI0N2M1YTI2NTE2ZTM4ZTk5YTI1OWNkMjFmZjViODQwOTc1MzNkNjpDbUVLWHhKZENnaHRZWE4wWlhJdE1CQUJRazhLQ1hObFkzQXlOVFpyTVJJZ3A2QmkwY0NKUm5PUVF4ZUw1UlhqVzd2MXpLSnJKUTdXY294eTVPR1YzSGthSUQzZ3N1ODZfc0ZEMDlzV2VSN084YVFDVmVsdWRUVzVhWVNQb1BpUUVnQmkjbWFzdGVyLTAiLCJ0eXAiOiJKV1QifQ.eyJpYXQiOjE3MzY1MTEzNzcsImlzcyI6ImRpZDpwcmlzbTozOGU3OGU0ZGQ5NzExYzQ5YTFmYjUzM2IwMjQ3YzVhMjY1MTZlMzhlOTlhMjU5Y2QyMWZmNWI4NDA5NzUzM2Q2OkNtRUtYeEpkQ2dodFlYTjBaWEl0TUJBQlFrOEtDWE5sWTNBeU5UWnJNUklncDZCaTBjQ0pSbk9RUXhlTDVSWGpXN3YxektKckpRN1djb3h5NU9HVjNIa2FJRDNnc3U4Nl9zRkQwOXNXZVI3TzhhUUNWZWx1ZFRXNWFZU1BvUGlRRWdCaSIsIm5iZiI6MTczNjUxMTM3NzY0MywidnAiOnsiQGNvbnRleHQiOlsiaHR0cHM6Ly93d3cudzMub3JnLzIwMTgvY3JlZGVudGlhbHMvdjEiXSwidHlwZSI6WyJWZXJpZmlhYmxlUHJlc2VudGF0aW9uIl0sInZlcmlmaWFibGVDcmVkZW50aWFsIjpbImV5SmhiR2NpT2lKRlV6STFOa3NpTENKMGVYQWlPaUpLVjFRaWZRLmV5SnBZWFFpT2pFM016WTFNVEV6Tnpjc0ltVjRjQ0k2TVRjek9URTRPVGMzTnpZeU5pd2lhWE56SWpvaVpHbGtPbkJ5YVhOdE9qQTROVGhqTXpCa1lXWTJaREJqWXpObE1HSXhZV1V6TVdJM1kySXlNVEptTkRRME5tRTJaVEJtTkRkaE5Ua3lObUl6WW1FM1pHTTJORGs0TmpFMU4ySTZRMjFGUzFoNFNtUkRaMmgwV1ZoT01GcFlTWFJOUWtGQ1VXczRTME5ZVG14Wk0wRjVUbFJhY2sxU1NXZE5NMGg0YkVWU0xVaE9TRWMxT1U1QlIyOUtTamRQWkVFMVdHeFJRV0pQVlRWS2NWQkhiVzVyV21sUllVbEdXV281VVZWVGIxOTRhV1Z0V1dsTVNHeENRbXQzU0dwYVdrdFNNVVpxVTBFeVQyeG5TMGs1YVVNNUlpd2libUptSWpveE56TTJOVEV4TXpjM05qSTJMQ0p6ZFdJaU9pSmthV1E2Y0hKcGMyMDZNemhsTnpobE5HUmtPVGN4TVdNME9XRXhabUkxTXpOaU1ESTBOMk0xWVRJMk5URTJaVE00WlRrNVlUSTFPV05rTWpGbVpqVmlPRFF3T1RjMU16TmtOanBEYlVWTFdIaEtaRU5uYUhSWldFNHdXbGhKZEUxQ1FVSlJhemhMUTFoT2JGa3pRWGxPVkZweVRWSkpaM0EyUW1rd1kwTktVbTVQVVZGNFpVdzFVbGhxVnpkMk1YcExTbkpLVVRkWFkyOTRlVFZQUjFZelNHdGhTVVF6WjNOMU9EWmZjMFpFTURselYyVlNOMDg0WVZGRFZtVnNkV1JVVnpWaFdWTlFiMUJwVVVWblFta2lMQ0oyWXlJNmV5SkFZMjl1ZEdWNGRDSTZXeUpvZEhSd2N6b3ZMM2QzZHk1M015NXZjbWN2TWpBeE9DOWpjbVZrWlc1MGFXRnNjeTkyTVNKZExDSjBlWEJsSWpwYklsWmxjbWxtYVdGaWJHVkRjbVZrWlc1MGFXRnNJbDBzSW1semMzVmxjaUk2SW1ScFpEcHdjbWx6YlRvd09EVTRZek13WkdGbU5tUXdZMk16WlRCaU1XRmxNekZpTjJOaU1qRXlaalEwTkRaaE5tVXdaalEzWVRVNU1qWmlNMkpoTjJSak5qUTVPRFl4TlRkaU9rTnRSVXRZZUVwa1EyZG9kRmxZVGpCYVdFbDBUVUpCUWxGck9FdERXRTVzV1ROQmVVNVVXbkpOVWtsblRUTkllR3hGVWkxSVRraEhOVGxPUVVkdlNrbzNUMlJCTlZoc1VVRmlUMVUxU25GUVIyMXVhMXBwVVdGSlJsbHFPVkZWVTI5ZmVHbGxiVmxwVEVoc1FrSnJkMGhxV2xwTFVqRkdhbE5CTWs5c1owdEpPV2xET1NJc0ltbHpjM1ZoYm1ObFJHRjBaU0k2SWpJd01qVXRNREV0TVRCVU1USTZNVFk2TVRjdU5qSTJXaUlzSW1OeVpXUmxiblJwWVd4VGRXSnFaV04wSWpwN0ltTnZkWEp6WlNJNklrbGtaVzUwZFhNZ1ZISmhhVzVwYm1jZ1kyOTFjbk5sSUVObGNuUnBabWxqWVhScGIyNGdNakF5TkNKOWZYMC5TR3BrUjdZRy15WXlOeFo0d19VRjF5dWV1MmdmNzNSLUJrdEtYX21QWnRoMnM0cE9kcjVORnV6M002ZmFGQlpLVGJENXlkR0hGRTRiYldMazA5dkIyZyJdfX0.pt8bSbXa6AhZ_dMuDZ_iXW4F6R9EmS-BGZ7ETb9kuOdxWfsN0yjKkc-cNSMvXUha2NlJ2Q34Yrms53xo4eNLlQ", + ], + }; + + const presentationRequest: DIF.Presentation.Request = { + presentation_definition: { + id: "ba7814d4-4bdf-42a5-a3f9-a2e86419e6c4", + input_descriptors: [ + { + id: "9e50eb6b-e7fc-46a8-bd91-7d53ac4adc53", + name: "Presentation", + purpose: "Verifying Credentials", + constraints: { + fields: [ + { + path: [ + "$.vc.credentialSubject.course", + "$.credentialSubject.course", + "$.course", + ], + id: "c9c60d68-a25d-4ab0-8968-8d270ad95590", + optional: false, + filter: { + type: "string", + pattern: "Identus Training course Certification 2024", + }, + name: "course", + }, + { + path: [ + "$.vc.issuer", + "$.issuer", + "$.iss", + "$.vc.iss", + ], + id: "6a6fa378-d701-43e7-81d5-ee1cd80c585e", + optional: false, + name: "issuer", + filter: { + type: "string", + pattern: "did:prism:0858c30daf6d0cc3e0b1ae31b7cb212f4446a6e0f47a5926b3ba7dc64986157b:CmEKXxJdCghtYXN0ZXItMBABQk8KCXNlY3AyNTZrMRIgM3HxlER-HNHG59NAGoJJ7OdA5XlQAbOU5JqPGmnkZiQaIFYj9QUSo_xiemYiLHlBBkwHjZZKR1FjSA2OlgKI9iC9", + }, + }, + ], + limit_disclosure: "required", + }, + format: { + jwt: { + alg: [ + "ES256K", + ], + }, + }, + }, + ], + format: { + jwt: { + alg: [ + "ES256K", + ], + }, + }, + }, + options: { + challenge: "sign this", + domain: "N/A", + }, + }; + + test("returns Payload ('valid', true)", async () => { + const sut = new PresentationVerify({ presentation, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.pid).toBe("valid"); + expect(result.data).toBe(true); + }); + + describe("validation", () => { + test("invalid presentation - not an object - returns false", async () => { + const presentation = "fail" as any; + const sut = new PresentationVerify({ presentation, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.data).toBe(false); + }); + + test("invalid presentation - missing presentation_submission - returns false", async () => { + const invalid = { ...presentation, presentation_submission: undefined } as any; + const sut = new PresentationVerify({ presentation: invalid, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.data).toBe(false); + }); + + test("invalid presentation - no matching format - returns false", async () => { + const invalid: DIF.EmbedTarget = { + ...presentation, + presentation_submission: { + ...presentation.presentation_submission, + descriptor_map: [{ + format: "invalid" as any, + id: "9e50eb6b-e7fc-46a8-bd91-7d53ac4adc53", + path: "$.verifiablePresentation[0]", + path_nested: { + id: "9e50eb6b-e7fc-46a8-bd91-7d53ac4adc53", + format: "jwt_vc", + path: "$.vp.verifiableCredential[0]", + }, + }, + ], + }, + }; + + const sut = new PresentationVerify({ presentation: invalid, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.data).toBe(false); + }); + + test("invalid presentationRequest - not an object - returns false", async () => { + const sut = new PresentationVerify({ presentation, presentationRequest: "fail" as any }); + const result = await ctx.run(sut); + + expect(result.data).toBe(false); + }); + }); + }); + }); + + describe("SDJWT", () => { + const presentation: DIF.EmbedTarget = { + presentation_submission: { + id: "f28b346e-c20e-4651-8c24-7f41a576cf26", + definition_id: "acd86273-5017-4980-a9be-dab6c725c811", + descriptor_map: [ + { + id: "c3fd00a4-129d-49dc-a640-d84ad32826d9", + format: "sdjwt" as any, + path: "$.verifiablePresentation[0]", + }, + ], + }, + verifiablePresentation: [ + "eyJ0eXAiOiJ2YytzZC1qd3QiLCJraWQiOiJkaWQ6cHJpc206YjNiOTNhOGYzODI0YmMzOTMzZDAyMmFmNjhhNTU4NWM2MmE1ZjRhNmQ1MzlkZmU0YjQxYzA5NTZkYzAwMWUxNzpDajBLT3hJNUNnaHRZWE4wWlhJdE1CQUJTaXNLQjBWa01qVTFNVGtTSUhQNC0ycGF1dzRRX2RPc2tXZHdYLUZNdHBEMFFLcUVhcWxJTnNQbDJ4bk4jbWFzdGVyLTAiLCJhbGciOiJlZGRzYSJ9.eyJpc3MiOiJkaWQ6cHJpc206YjNiOTNhOGYzODI0YmMzOTMzZDAyMmFmNjhhNTU4NWM2MmE1ZjRhNmQ1MzlkZmU0YjQxYzA5NTZkYzAwMWUxNzpDajBLT3hJNUNnaHRZWE4wWlhJdE1CQUJTaXNLQjBWa01qVTFNVGtTSUhQNC0ycGF1dzRRX2RPc2tXZHdYLUZNdHBEMFFLcUVhcWxJTnNQbDJ4bk4iLCJuYmYiOjE3MzY1MTQ4OTk3OTQsImV4cCI6MTczOTE5MzI5OTc5NCwic3ViIjoiZGlkOnByaXNtOjIyZWRlZDQ3MDYyNzRkZGQwYzIzNmNkZjNiNTY1YjBkODQ3MzFkM2I4MGQ3M2JiYzJhMTI1ZTQzMGQ2NjJhZmY6Q2owS094STVDZ2h0WVhOMFpYSXRNQkFCU2lzS0IwVmtNalUxTVRrU0lQX1JFaWF2cHp6WXlydVJYZVdHdFhJcTdMR2QxaEgxeHJUalRHRGRUdWlvIiwidmN0IjoiZGlkOnByaXNtOmIzYjkzYThmMzgyNGJjMzkzM2QwMjJhZjY4YTU1ODVjNjJhNWY0YTZkNTM5ZGZlNGI0MWMwOTU2ZGMwMDFlMTc6Q2owS094STVDZ2h0WVhOMFpYSXRNQkFCU2lzS0IwVmtNalUxTVRrU0lIUDQtMnBhdXc0UV9kT3NrV2R3WC1GTXRwRDBRS3FFYXFsSU5zUGwyeG5OIiwiX3NkIjpbIk1FZHljRFg0UlFyTGRxdlcxcmFCck1DRFZxZDJLUEhkZWRqbEFuLW1kZ2MiXSwiX3NkX2FsZyI6InNoYTI1NiJ9.Y9y1hoRxZLxuBMEhSCr0Jp37DNX7ebUIDjCxys_DS_x21Ka2w1N-c7kIPZhfM7p1ptfJOmYMpJkbuP6FotXPAA~WyI1NjJkNmE2MTg2NGYwMTZhIiwidmMiLHsiX3NkIjpbIkJaUy1hN2RPM3FKdUE5V2lsdEduUFpWd0xXdUhjNjhhSFNpWWc4WDFsdkEiLCJCdllZTzhtWFRubnZJYTVEc2ZybjA0UHR3V1pnS1d4bkFQQWdDLWNWb3lJIiwiU0Y1VTRlYm0ySzhhQ2pLS0JfRkg3N09sQ1VSeTFQY0JqNEJDZ2t5M3Y5SSIsIlVxVzlFVS1tVWpFMjZDd290VW51aG5iQ19EdkNYYkxmZFQzM05rek5lWEEiLCJubkZlRVFQUEJNQW12WkNVbVU4MkU5X0Z3YWx1aUg2aTRPRkx3VndxaFVrIl19XQ~WyIzZjA2OTU1YzBjZGU4NWY2IiwiY3JlZGVudGlhbFN1YmplY3QiLHsiX3NkIjpbIkY1N1JVdGpLZkRvZFZlUnF0RlQtdnZNZVdhVENmZG1wM1FYODZfQ3p2aGsiLCJRN0tWVnBEZ28zNHhNVXBfZXdyeVptaWJJdzdBb2ZlNWNTV2g3X0U0ZFJJIl19XQ~WyJmNjdkNDA4NmIwNGQ3NmI3IiwiZmlyc3RuYW1lIiwiaG9sYSJd~", + ], + }; + + const presentationRequest: DIF.Presentation.Request = { + presentation_definition: { + id: "acd86273-5017-4980-a9be-dab6c725c811", + input_descriptors: [ + { + id: "c3fd00a4-129d-49dc-a640-d84ad32826d9", + name: "Presentation", + purpose: "Verifying Credentials", + constraints: { + fields: [ + { + path: [ + "$.vc.credentialSubject.firstname", + "$.credentialSubject.firstname", + "$.firstname", + ], + id: "2c46f1ee-ac0a-4ba0-9e8d-84dd63f5b407", + optional: false, + filter: { + type: "string", + pattern: "hola", + }, + name: "firstname", + }, + ], + limit_disclosure: "required", + }, + format: { + sdjwt: { + alg: [ + "EdDSA", + ], + }, + }, + }, + ], + format: { + sdjwt: { + alg: [ + "EdDSA", + ], + }, + }, + }, + options: {}, + }; + + test("returns Payload ('valid', true)", async () => { + const sut = new PresentationVerify({ presentation, presentationRequest }); + const result = await ctx.run(sut); + + expect(result.pid).toBe("valid"); + expect(result.data).toBe(true); + }); + + }); +}); diff --git a/tests/plugins/oea/jwt/CredentialIssue.test.ts b/tests/plugins/oea/jwt/CredentialIssue.test.ts new file mode 100644 index 000000000..c26298dec --- /dev/null +++ b/tests/plugins/oea/jwt/CredentialIssue.test.ts @@ -0,0 +1,42 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { Apollo, Castor } from '../../../../src'; +import { JWT, SDJWT } from "../../../../src/pollux/utils/jwt"; +import { CredentialIssue } from '../../../../src/plugins/internal/oea/jwt'; +import { JWTCredential } from '../../../../src'; +import { Task } from '../../../../src/utils'; +import * as Fixtures from "../../../fixtures"; + +describe("Plugins - OEA", () => { + let ctx: Task.Context; + + beforeEach(() => { + const apollo = new Apollo(); + const castor = new Castor(apollo); + ctx = Task.Context.make({ + Apollo: apollo, + Castor: castor, + JWT: new JWT(), + SDJWT: new SDJWT(), + }); + }); + + describe("JWT", () => { + describe("CredentialIssue", () => { + test("data passed as Buffer - credential returned", async () => { + const data = Buffer.from(Fixtures.Credentials.JWT.credentialPayloadEncoded); + const result = await ctx.run(new CredentialIssue({ data })); + + expect(result.pid).toBe("credential"); + expect(result.data).toBeInstanceOf(JWTCredential); + }); + + test("data passed as string - credential returned", async () => { + const data = Fixtures.Credentials.JWT.credentialPayloadEncoded; + const result = await ctx.run(new CredentialIssue({ data })); + + expect(result.pid).toBe("credential"); + expect(result.data).toBeInstanceOf(JWTCredential); + }); + }); + }); +}); diff --git a/tests/plugins/oea/sdjwt/CredentialIssue.test.ts b/tests/plugins/oea/sdjwt/CredentialIssue.test.ts new file mode 100644 index 000000000..dce640ebc --- /dev/null +++ b/tests/plugins/oea/sdjwt/CredentialIssue.test.ts @@ -0,0 +1,41 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { Apollo, Castor, SDJWTCredential } from '../../../../src'; +import { JWT, SDJWT } from "../../../../src/pollux/utils/jwt"; +import { CredentialIssue } from '../../../../src/plugins/internal/oea/sdjwt'; +import { Task } from '../../../../src/utils'; +import * as Fixtures from "../../../fixtures"; + +describe("Plugins - OEA", () => { + let ctx: Task.Context; + + beforeEach(() => { + const apollo = new Apollo(); + const castor = new Castor(apollo); + ctx = Task.Context.make({ + Apollo: apollo, + Castor: castor, + JWT: new JWT(), + SDJWT: new SDJWT(), + }); + }); + + describe("SDJWT", () => { + describe("CredentialIssue", () => { + test("data passed as Buffer - credential returned", async () => { + const data = Buffer.from(Fixtures.Credentials.JWT.credentialPayloadEncoded); + const result = await ctx.run(new CredentialIssue({ data })); + + expect(result.pid).toBe("credential"); + expect(result.data).toBeInstanceOf(SDJWTCredential); + }); + + test("data passed as string - credential returned", async () => { + const data = Fixtures.Credentials.JWT.credentialPayloadEncoded; + const result = await ctx.run(new CredentialIssue({ data })); + + expect(result.pid).toBe("credential"); + expect(result.data).toBeInstanceOf(SDJWTCredential); + }); + }); + }); +}); diff --git a/tests/plugins/oea/sdjwt/PresentationRequest.test.ts b/tests/plugins/oea/sdjwt/PresentationRequest.test.ts new file mode 100644 index 000000000..28d50a8b2 --- /dev/null +++ b/tests/plugins/oea/sdjwt/PresentationRequest.test.ts @@ -0,0 +1,69 @@ +import { vi, describe, expect, test, beforeEach } from 'vitest'; +import { JWTCredential, SDJWTCredential } from '../../../../src'; +import { JWT, SDJWT } from "../../../../src/pollux/utils/jwt"; +import { PresentationRequest } from '../../../../src/plugins/internal/oea/sdjwt'; +import { Task } from '../../../../src/utils'; +import { OEA } from '../../../../src/plugins/internal/oea/types'; +import { Pluto } from '../../../../src/domain'; +import { mockPluto } from '../../../fixtures/inmemory/factory'; +import { CannotFindDIDPrivateKey } from '../../../../src/domain/models/errors/Agent'; +import * as Fixtures from "../../../fixtures"; + +describe("Plugins - OEA", () => { + let ctx: Task.Context; + let pluto: Pluto; + + beforeEach(() => { + pluto = mockPluto(); + + ctx = Task.Context.make({ + Pluto: pluto, + JWT: new JWT(), + SDJWT: new SDJWT(), + }); + }); + + describe("SDJWT", () => { + describe("PresentationRequest", () => { + test("Payload returned (OEA.PRISM_SDJWT, JWS)", async () => { + vi.spyOn(pluto, "getDIDPrivateKeysByDID").mockResolvedValue([Fixtures.Keys.ed25519.privateKey]); + + const credential = SDJWTCredential.fromJWS(Fixtures.Credentials.JWT.credentialPayloadEncoded); + const presentationRequest = {} as any; + const result = await ctx.run(new PresentationRequest({ credential, presentationRequest })); + + expect(result.pid).toEqual(OEA.PRISM_SDJWT); + expect(result.data).toEqual(expect.stringContaining("")); + }); + + test("credential not SDJWTCredential - throws", async () => { + const credential = JWTCredential.fromJWS(Fixtures.Credentials.JWT.credentialPayloadEncoded); + const presentationRequest = {} as any; + + const sut = ctx.run(new PresentationRequest({ credential, presentationRequest })); + + expect(sut).rejects.toThrow(); + }); + + test("privateKey not found - throws", async () => { + vi.spyOn(pluto, "getDIDPrivateKeysByDID").mockResolvedValue([]); + const credential = SDJWTCredential.fromJWS(Fixtures.Credentials.JWT.credentialPayloadEncoded); + const presentationRequest = {} as any; + + const sut = ctx.run(new PresentationRequest({ credential, presentationRequest })); + + expect(sut).rejects.toThrowError(CannotFindDIDPrivateKey); + }); + + test("Ed25519 privateKey not found - throws", async () => { + vi.spyOn(pluto, "getDIDPrivateKeysByDID").mockResolvedValue([Fixtures.Keys.secp256K1.privateKey]); + const credential = SDJWTCredential.fromJWS(Fixtures.Credentials.JWT.credentialPayloadEncoded); + const presentationRequest = {} as any; + + const sut = ctx.run(new PresentationRequest({ credential, presentationRequest })); + + expect(sut).rejects.toThrowError(CannotFindDIDPrivateKey); + }); + }); + }); +}); diff --git a/tests/pollux/Pollux.revocation.test.ts b/tests/pollux/Pollux.revocation.test.ts index 983198195..8b915a0df 100644 --- a/tests/pollux/Pollux.revocation.test.ts +++ b/tests/pollux/Pollux.revocation.test.ts @@ -8,13 +8,16 @@ import { base64 } from "multiformats/bases/base64"; import { VerificationKeyType } from "../../src/castor/types"; import * as Fixtures from "../fixtures"; import { SDJWTCredential } from "../../src/pollux/models/SDJWTVerifiableCredential"; -import { PlugPol } from "../../src/pollux/PlugPol"; -import DIFModule from "../../src/pollux/plugins/dif"; +import DIFPlugin from "../../src/plugins/internal/dif"; import SinonChai from "sinon-chai"; import chai from "chai"; import chaiAsPromised from "chai-as-promised"; +import { Agent } from '../../src'; +import { Task } from '../../src/utils'; +import { RunProtocol } from '../../src/edge-agent/helpers/RunProtocol'; +import { PluginManager } from '../../src/plugins'; chai.use(SinonChai); chai.use(chaiAsPromised); @@ -25,7 +28,8 @@ vi.mock("pako"); describe("Pollux", () => { let api: Api; let apollo: Apollo; - let pollux: PlugPol; + let plugins: PluginManager; + let testCtx: Task.Context; beforeEach(async () => { const pakoPkg = await import('pako'); @@ -33,12 +37,13 @@ describe("Pollux", () => { api = { request: async () => new ApiResponse(new Uint8Array(), 200) }; apollo = new Apollo(); - pollux = new PlugPol({ + plugins = new PluginManager(); + plugins.register(DIFPlugin); + testCtx = Task.Context.make({ Api: api, - Apollo: apollo + Apollo: apollo, + Plugins: plugins, }); - await pollux.start(); - pollux.register(DIFModule); }); afterEach(async () => { @@ -89,14 +94,27 @@ describe("Pollux", () => { vc3.credentialStatus.statusListIndex = 3; credential3.properties.set(JWT_VC_PROPS.vc, vc3); - const revoked = await pollux.handle("revocation", "prism/jwt", {credential: credential}); - const revoked2 = await pollux.handle("revocation", "prism/jwt", {credential: credential2}); - const revoked3 = await pollux.handle("revocation", "prism/jwt", {credential: credential3}); - - expect(revoked).to.eq(true) - expect(revoked2).to.eq(true) - expect(revoked3).to.eq(false) - + const revoked = await testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential } + })); + + const revoked2 = await testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: credential2 } + })); + + const revoked3 = await testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: credential3 } + })); + + expect(revoked.data).to.eq(true) + expect(revoked2.data).to.eq(true) + expect(revoked3.data).to.eq(false) }) it("Should throw an error if we try to use a SDJWT credential ", async () => { @@ -129,8 +147,13 @@ describe("Pollux", () => { ); const credential = SDJWTCredential.fromJWS(Fixtures.Credentials.SDJWT.credentialPayloadEncoded); - await expect(pollux.handle("revocation", "prism/jwt", {credential: credential})) - .to.eventually.rejectedWith(`Only JWT Credential are supported`) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential } + })); + + await expect(sut).to.eventually.rejectedWith(`Only JWT Credential are supported`) }) it("Should throw an error if we try to use a credential which an unsupported type ", async () => { @@ -168,12 +191,21 @@ describe("Pollux", () => { vc.credentialStatus.type = "Wrong" credential.properties.set(JWT_VC_PROPS.vc, vc); - await expect(pollux.handle("revocation", "prism/jwt", {credential: credential})) - .to.eventually.rejectedWith(`CredentialStatus revocation type not supported`) - - const sut2 = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut2})) - .to.eventually.rejectedWith(`CredentialStatus response revocation type not supported`) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential } + })); + + await expect(sut).to.eventually.rejectedWith(`CredentialStatus revocation type not supported`) + + const sut2 = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut2).to.eventually.rejectedWith(`CredentialStatus response revocation type not supported`) }) it("Should throw an error if the credential status proof contains an invalid verificationMethod", async () => { @@ -206,8 +238,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith(`CredentialStatus proof invalid verificationMethod`) }) @@ -242,8 +279,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith("No public jwk provided") }) @@ -277,8 +319,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith("Err Only EcdsaSecp256k1VerificationKey2019 is supported") @@ -314,8 +361,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith("Err Invalid JWK kty: undefined, should be EC") }) @@ -350,11 +402,14 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) - .to.eventually.rejectedWith("Err Invalid JWK crv: undefined, should be secp256k1") - + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + await expect(sut) + .to.eventually.rejectedWith("Err Invalid JWK crv: undefined, should be secp256k1") }) it("Should throw an eror if an invalid verificationKey is used", async () => { @@ -392,8 +447,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith("CredentialStatus proof invalid verifying key") }) @@ -428,8 +488,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith("Credential status jwt is invalid") }) @@ -482,8 +547,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith("CredentialStatus invalid signature") }) @@ -526,8 +596,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith(`Couldn't ungzip base64 encoded list, err: whatever`) }) @@ -562,8 +637,13 @@ describe("Pollux", () => { }, 200) ); - const sut = JWTCredential.fromJWS(revocableJWTCredential); - await expect(pollux.handle("revocation", "prism/jwt", {credential: sut})) + const sut = testCtx.run(new RunProtocol({ + type: "revocation-check", + pid: "prism/jwt", + data: { credential: JWTCredential.fromJWS(revocableJWTCredential) } + })); + + await expect(sut) .to.eventually.rejectedWith("CredentialStatus proof type not supported") }) }); diff --git a/vitest.config.mjs b/vitest.config.mjs index 44102acea..e8f45a330 100644 --- a/vitest.config.mjs +++ b/vitest.config.mjs @@ -22,7 +22,7 @@ const testConfig = { reporters: ['verbose'], coverage: { provider: 'istanbul', - reporter: isCI ? ['json-summary', 'lcov'] : ['json-summary', "html"], + reporter: isCI ? ['json-summary', 'lcov'] : ['text', 'lcov'], thresholds: { "branches": 63, "functions": 75,