From 099de34a7f785550bee17d18379ff9924b66e997 Mon Sep 17 00:00:00 2001 From: Curtish Date: Thu, 29 Aug 2024 12:30:35 +0100 Subject: [PATCH] feat: seaprate DIDCommAgent and adding Tasks Signed-off-by: Curtish --- src/apollo/Apollo.ts | 8 +- src/domain/models/errors/Common.ts | 4 + src/domain/utils/JWT.ts | 6 +- src/edge-agent/Agent.Backup.ts | 7 +- src/edge-agent/Agent.Credentials.ts | 588 ----------------- src/edge-agent/Agent.DIDHigherFunctions.ts | 201 ------ src/edge-agent/Agent.Invitations.ts | 195 ------ src/edge-agent/Agent.ts | 499 ++------------- .../connectionsManager/ConnectionsManager.ts | 18 +- src/edge-agent/didFunctions/CreateJwt.ts | 63 ++ src/edge-agent/didFunctions/CreatePrismDID.ts | 76 +++ .../PrismKeyPathIndex.ts} | 22 +- src/edge-agent/didFunctions/Sign.ts | 32 + src/edge-agent/didFunctions/index.ts | 3 + src/edge-agent/didcomm/Agent.ts | 599 ++++++++++++++++++ src/edge-agent/didcomm/Context.ts | 12 + src/edge-agent/didcomm/CreatePeerDID.ts | 66 ++ src/edge-agent/didcomm/CreatePresentation.ts | 178 ++++++ .../didcomm/CreatePresentationRequest.ts | 97 +++ .../didcomm/HandleIssueCredential.ts | 57 ++ .../didcomm/HandleOfferCredential.ts | 170 +++++ src/edge-agent/didcomm/HandlePresentation.ts | 51 ++ src/edge-agent/types/index.ts | 78 +-- src/index.ts | 8 +- src/pollux/Pollux.ts | 1 + src/pollux/models/JWTVerifiableCredential.ts | 53 +- src/pollux/utils/JWT.ts | 2 + src/pollux/utils/SDJWT.ts | 4 +- src/pollux/utils/decodeJWS.ts | 21 - src/pollux/utils/jwt/JWTCore.ts | 17 +- src/utils/guards.ts | 35 +- src/utils/tasks.ts | 165 +++++ src/utils/types.ts | 7 + tests/agent/Agent.ConnectionsManager.test.ts | 9 +- tests/agent/Agent.test.ts | 122 ++-- tests/agent/mocks/ConnectionManagerMock.ts | 170 ----- tests/pollux/Bitstring.test.ts | 6 +- 37 files changed, 1790 insertions(+), 1860 deletions(-) delete mode 100644 src/edge-agent/Agent.Credentials.ts delete mode 100644 src/edge-agent/Agent.DIDHigherFunctions.ts delete mode 100644 src/edge-agent/Agent.Invitations.ts create mode 100644 src/edge-agent/didFunctions/CreateJwt.ts create mode 100644 src/edge-agent/didFunctions/CreatePrismDID.ts rename src/edge-agent/{Agent.PrismKeyPathIndexTask.ts => didFunctions/PrismKeyPathIndex.ts} (56%) create mode 100644 src/edge-agent/didFunctions/Sign.ts create mode 100644 src/edge-agent/didFunctions/index.ts create mode 100644 src/edge-agent/didcomm/Agent.ts create mode 100644 src/edge-agent/didcomm/Context.ts create mode 100644 src/edge-agent/didcomm/CreatePeerDID.ts create mode 100644 src/edge-agent/didcomm/CreatePresentation.ts create mode 100644 src/edge-agent/didcomm/CreatePresentationRequest.ts create mode 100644 src/edge-agent/didcomm/HandleIssueCredential.ts create mode 100644 src/edge-agent/didcomm/HandleOfferCredential.ts create mode 100644 src/edge-agent/didcomm/HandlePresentation.ts delete mode 100644 src/pollux/utils/decodeJWS.ts create mode 100644 src/utils/tasks.ts delete mode 100644 tests/agent/mocks/ConnectionManagerMock.ts diff --git a/src/apollo/Apollo.ts b/src/apollo/Apollo.ts index 11b2bdf51..933afca64 100644 --- a/src/apollo/Apollo.ts +++ b/src/apollo/Apollo.ts @@ -213,11 +213,11 @@ export default class Apollo implements ApolloInterface, KeyRestoration { const keyCurve = parameters[KeyProperties.curve]; if (isEmpty(keyType)) { - throw new ApolloError.InvalidKeyType(keyType); + throw new ApolloError.InvalidKeyType(); } if (isEmpty(keyCurve)) { - throw new ApolloError.InvalidKeyCurve(keyCurve); + throw new ApolloError.InvalidKeyCurve(); } const { curve } = getKeyCurveByNameAndIndex(keyCurve); @@ -325,11 +325,11 @@ export default class Apollo implements ApolloInterface, KeyRestoration { const keyCurve = parameters[KeyProperties.curve]; if (isEmpty(keyType)) { - throw new ApolloError.InvalidKeyType(keyType); + throw new ApolloError.InvalidKeyType(); } if (isEmpty(keyCurve)) { - throw new ApolloError.InvalidKeyCurve(keyCurve); + throw new ApolloError.InvalidKeyCurve(); } const { curve } = getKeyCurveByNameAndIndex(parameters[KeyProperties.curve]); diff --git a/src/domain/models/errors/Common.ts b/src/domain/models/errors/Common.ts index b8966d939..6739f67b2 100644 --- a/src/domain/models/errors/Common.ts +++ b/src/domain/models/errors/Common.ts @@ -8,3 +8,7 @@ export class SDKError extends Error { export class UnknownError extends SDKError { constructor() { super(-1, "Something went wrong"); } } + +export class ExpectError extends SDKError { + constructor(msg?: string) { super(-1, msg ?? "value should exist"); } +} diff --git a/src/domain/utils/JWT.ts b/src/domain/utils/JWT.ts index e89d3326f..c795ce8f7 100644 --- a/src/domain/utils/JWT.ts +++ b/src/domain/utils/JWT.ts @@ -2,6 +2,8 @@ import { JWTPayload, Signer, createJWT } from "did-jwt"; import { base64url } from "multiformats/bases/base64"; import { DID, PrivateKey } from ".."; import { asJsonObj, isNil } from "../../utils/guards"; +// ??? shouldnt be importing Pollux error +import { InvalidJWTString } from "../models/errors/Pollux"; export namespace JWT { export interface Header { @@ -65,9 +67,7 @@ export namespace JWT { const payloadEnc = parts.at(1); if (parts.length != 3 || isNil(headersEnc) || isNil(payloadEnc)) { - // TODO error - // throw new InvalidJWTString(); - throw new Error(); + throw new InvalidJWTString(); } const headers = base64url.baseDecode(headersEnc); diff --git a/src/edge-agent/Agent.Backup.ts b/src/edge-agent/Agent.Backup.ts index c4b6347f5..f21c11b16 100644 --- a/src/edge-agent/Agent.Backup.ts +++ b/src/edge-agent/Agent.Backup.ts @@ -2,9 +2,14 @@ import * as Domain from "../domain"; import Agent from "./Agent"; import { isObject, validateSafe } from "../utils"; +/** + * define Agent requirements for Backup + */ +type BackupAgent = Pick; + export class AgentBackup { constructor( - public readonly Agent: Agent + public readonly Agent: BackupAgent ) {} /** diff --git a/src/edge-agent/Agent.Credentials.ts b/src/edge-agent/Agent.Credentials.ts deleted file mode 100644 index ff6186436..000000000 --- a/src/edge-agent/Agent.Credentials.ts +++ /dev/null @@ -1,588 +0,0 @@ -import { base64 } from "multiformats/bases/base64"; - -import { - AgentError, - Apollo, - AttachmentDescriptor, - Castor, - Credential, - CredentialType, - CredentialIssueOptions, - Curve, - DID, - KeyProperties, - KeyTypes, - Seed, - Message, - Pluto, - Pollux, - CredentialMetadata, - PresentationOptions, - Mercury, - PresentationDefinitionRequest, - PresentationClaims, - AttachmentFormats, - PolluxError, - curveToAlg, -} from "../domain"; - -import { AnonCredsCredential } from "../pollux/models/AnonCredsVerifiableCredential"; -import { JWTCredential } from "../pollux/models/JWTVerifiableCredential"; -import { PresentationRequest } from "../pollux/models/PresentationRequest"; - -import { OfferCredential } from "./protocols/issueCredential/OfferCredential"; -import { createRequestCredentialBody, RequestCredential } from "./protocols/issueCredential/RequestCredential"; -import { IssueCredential } from "./protocols/issueCredential/IssueCredential"; -import { Presentation } from "./protocols/proofPresentation/Presentation"; -import { RequestPresentation } from "./protocols/proofPresentation/RequestPresentation"; - -import { AgentCredentials as AgentCredentialsClass, AgentDIDHigherFunctions } from "./types"; -import { PrismKeyPathIndexTask } from "./Agent.PrismKeyPathIndexTask"; -import { uuid } from "@stablelib/uuid"; -import { ProtocolType } from "./protocols/ProtocolTypes"; -import { validatePresentationClaims } from "../pollux/utils/claims"; -import { SDJWTCredential } from "../pollux/models/SDJWTVerifiableCredential"; - -export class AgentCredentials implements AgentCredentialsClass { - /** - * Creates an instance of AgentCredentials. - * - * @constructor - * @param {Apollo} apollo - * @param {Castor} castor - * @param {Pluto} pluto - * @param {Pollux} pollux - * @param {Seed} seed - */ - constructor( - protected apollo: Apollo, - protected castor: Castor, - protected pluto: Pluto, - protected pollux: Pollux, - protected seed: Seed, - protected mercury: Mercury, - protected agentDIDHigherFunctions: AgentDIDHigherFunctions - ) { } - - - - - - private createPresentationDefinitionRequest( - type: Type, - definition: PresentationDefinitionRequest, - from: DID, - to: DID - ) { - const attachmentFormat = type === CredentialType.JWT ? - AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS : - AttachmentFormats.ANONCREDS_PROOF_REQUEST; - - const attachments = [ - AttachmentDescriptor.build( - { - json: definition - }, - uuid(), - 'application/json', - undefined, - attachmentFormat - ) - ] - return new RequestPresentation( - { - proofTypes: [], - }, - attachments, - from, - to, - uuid() - ); - } - - async initiatePresentationRequest( - type: CredentialType, - toDID: DID, - claims: PresentationClaims - ): Promise { - const didDocument = await this.castor.resolveDID(toDID.toString()); - const newPeerDID = await this.agentDIDHigherFunctions.createNewPeerDID( - didDocument.services, - true - ); - - if (type === CredentialType.AnonCreds) { - if (!validatePresentationClaims(claims, CredentialType.AnonCreds)) { - throw new PolluxError.InvalidPresentationDefinitionError("Anoncreds Claims are invalid"); - } - - const presentationDefinitionRequest = await this.pollux.createPresentationDefinitionRequest( - type, - claims, - new PresentationOptions({}, CredentialType.AnonCreds) - ); - const request = await this.createPresentationDefinitionRequest( - type, - presentationDefinitionRequest, - newPeerDID, - toDID - ); - return request - } - - if (type === CredentialType.JWT) { - if (!validatePresentationClaims(claims, CredentialType.JWT)) { - throw new PolluxError.InvalidPresentationDefinitionError("JWT Claims are invalid"); - } - const presentationDefinitionRequest = await this.pollux.createPresentationDefinitionRequest( - type, - claims, - new PresentationOptions({ - jwt: { - jwtAlg: [curveToAlg(Curve.SECP256K1)] - }, - challenge: "Sign this text " + uuid(), - domain: 'N/A' - }) - ); - return this.createPresentationDefinitionRequest( - type, - presentationDefinitionRequest, - newPeerDID, - toDID - ); - } - - throw new PolluxError.CredentialTypeNotSupported(); - } - - async verifiableCredentials(): Promise { - return await this.pluto.getAllCredentials(); - } - - /** - * Extract the verifiableCredential object from the Issue credential message asyncronously - * - * @async - * @param {IssueCredential} message - * @returns {Promise} - */ - async processIssuedCredentialMessage( - issueCredential: IssueCredential - ): Promise { - const message = issueCredential.makeMessage() - const credentialType = message.credentialFormat; - const attachment = message.attachments.at(0); - - if (!attachment) { - throw new Error("No attachment"); - } - - if (!issueCredential.thid) { - throw new Error("No thid"); - } - - const parseOpts: CredentialIssueOptions = { - type: credentialType, - }; - - - const payload = typeof attachment.payload === 'string' ? attachment.payload : JSON.stringify(attachment.payload); - const credData = Uint8Array.from(Buffer.from(payload)); - - if (credentialType === CredentialType.AnonCreds) { - const linkSecret = await this.pluto.getLinkSecret(); - - parseOpts.linkSecret = linkSecret?.secret; - - const credentialMetadata = await this.pluto.getCredentialMetadata( - issueCredential.thid - ); - - if (!credentialMetadata || !credentialMetadata.isType(CredentialType.AnonCreds)) { - throw new Error("Invalid credential Metadata"); - } - - parseOpts.credentialMetadata = credentialMetadata.toJSON(); - } - - const credential: Credential = await this.pollux.parseCredential(credData, parseOpts); - - await this.pluto.storeCredential(credential); - - return credential; - } - - - - /** - * Asyncronously prepare a request credential message from a valid offerCredential for now supporting w3c verifiable credentials offers. - * - * @async - * @param {OfferCredential} offer - * @returns {Promise} - */ - async prepareRequestCredentialWithIssuer( - offer: OfferCredential - ): Promise { - const attachment = offer.attachments.at(0); - if (!attachment) { - throw new Error("Invalid attachment") - } - - const credentialType = offer.makeMessage().credentialFormat; - const payload = attachment.payload - let credRequestBuffer: string; - - const requestCredentialBody = createRequestCredentialBody( - [], - offer.body.goalCode, - offer.body.comment - ); - - const from = offer.to; - const to = offer.from; - if (!from) { - throw new Error("Missing from"); - } - if (!to) { - throw new Error("Missing to"); - } - const thid = offer.thid; - const credentialFormat = - credentialType === CredentialType.AnonCreds ? AttachmentFormats.ANONCREDS_REQUEST : - credentialType === CredentialType.JWT ? CredentialType.JWT : - credentialType === CredentialType.SDJWT ? CredentialType.SDJWT : - CredentialType.Unknown; - - if (credentialType === CredentialType.AnonCreds) { - const metaname = offer.thid; - if (!metaname) { - throw new Error("Missing offer.thid"); - } - - const linkSecret = await this.pluto.getLinkSecret(); - if (!linkSecret) { - throw new Error("No linkSecret available."); - } - - const [credentialRequest, credentialRequestMetadata] = - await this.pollux.processCredentialOffer(payload, { linkSecret }); - - credRequestBuffer = JSON.stringify(credentialRequest); - - const metadata = new CredentialMetadata(CredentialType.AnonCreds, metaname, credentialRequestMetadata); - - await this.pluto.storeCredentialMetadata(metadata); - } else if (credentialType === CredentialType.JWT) { - const getIndexTask = new PrismKeyPathIndexTask(this.pluto); - const privateKey = await this.apollo.createPrivateKey({ - [KeyProperties.curve]: Curve.SECP256K1, - [KeyProperties.index]: await getIndexTask.run(), - [KeyProperties.type]: KeyTypes.EC, - [KeyProperties.seed]: Buffer.from(this.seed.value).toString("hex"), - }); - - const did = await this.castor.createPrismDID(privateKey.publicKey()); - - await this.pluto.storeDID(did, privateKey); - - credRequestBuffer = await this.pollux.processCredentialOffer(payload, { - did: did, - keyPair: { - curve: Curve.SECP256K1, - privateKey: privateKey, - publicKey: privateKey.publicKey(), - }, - }); - } else if (credentialType === CredentialType.SDJWT) { - - const getIndexTask = new PrismKeyPathIndexTask(this.pluto); - const masterSk = await this.apollo.createPrivateKey({ - [KeyProperties.curve]: Curve.SECP256K1, - [KeyProperties.index]: await getIndexTask.run(), - [KeyProperties.type]: KeyTypes.EC, - [KeyProperties.seed]: Buffer.from(this.seed.value).toString("hex"), - }); - - const issSK = await this.apollo.createPrivateKey({ - [KeyProperties.curve]: Curve.ED25519, - [KeyProperties.index]: await getIndexTask.run(), - [KeyProperties.type]: KeyTypes.EC, - [KeyProperties.seed]: Buffer.from(this.seed.value).toString("hex"), - }); - - const did = await this.castor.createPrismDID( - masterSk.publicKey(), - [], - [ - issSK.publicKey() - ] - ); - - await this.pluto.storeDID(did, [masterSk, issSK]); - - credRequestBuffer = await this.pollux.processCredentialOffer(payload, { - did: did, - sdJWT: true, - keyPair: { - curve: Curve.SECP256K1, - privateKey: masterSk, - publicKey: masterSk.publicKey(), - }, - }); - } else { - throw new AgentError.InvalidCredentialFormats(); - } - - - const attachments = [ - new AttachmentDescriptor( - { - base64: base64.baseEncode(Buffer.from(credRequestBuffer)), - }, - credentialFormat, - undefined, - undefined, - // TODO: confirm what is the format that backend expects us to send AnonCreds VS JWT - credentialFormat - ), - ]; - - const requestCredential = new RequestCredential( - requestCredentialBody, - attachments, - from, - to, - thid - ); - - attachments.forEach((attachment) => { - requestCredential.body.formats.push({ - attach_id: attachment.id, - format: `${credentialFormat}`, - }); - }); - - return requestCredential; - } - - private async getPresentationDefinitionByThid(thid: string): Promise> { - const allMessages = (await this.pluto.getAllMessages()); - const message = allMessages.find((message) => { - return message.thid === thid && message.piuri === ProtocolType.DidcommRequestPresentation; - }); - if (message) { - const attachment = message.attachments.at(0); - if (!attachment) { - throw new AgentError.UnsupportedAttachmentType("Invalid presentation message, attachment missing"); - } - const presentationDefinitionRequest = Message.Attachment.extractJSON(attachment); - return presentationDefinitionRequest; - } - throw new AgentError.UnsupportedAttachmentType("Cannot find any message with that threadID"); - } - - async handlePresentation(presentation: Presentation): Promise { - const attachment = presentation.attachments.at(0); - if (!attachment) { - throw new AgentError.UnsupportedAttachmentType("Invalid presentation message, attachment missing"); - } - if (!presentation.thid) { - throw new AgentError.UnsupportedAttachmentType("Cannot find any message with that threadID"); - } - const presentationSubmission = typeof attachment.payload === 'string' ? - JSON.parse(attachment.payload) : - attachment.payload; - - const presentationDefinitionRequest = await this.getPresentationDefinitionByThid(presentation.thid); - const options = { - presentationDefinitionRequest - }; - const verified = await this.pollux.verifyPresentationSubmission( - presentationSubmission, - options - ); - return verified; - } - - /** - * Asyncronously create a verifiablePresentation from a valid stored verifiableCredential - * This is used when the verified requests a specific verifiable credential, this will create the actual - * instance of the presentation which we can share with the verifier. - * - * @async - * @param {RequestPresentation} message - * @param {VerifiableCredential} credential - * @returns {Promise} - */ - async createPresentationForRequestProof( - message: RequestPresentation, - credential: Credential - ): Promise { - const attachment = message.attachments.at(0); - if (!attachment) { - throw new AgentError.OfferDoesntProvideEnoughInformation(); - } - const attachmentFormat = attachment.format ?? 'unknown'; - const presentationRequest = this.parseProofRequest(attachment); - const proof = attachmentFormat === AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS ? - await this.handlePresentationDefinitionRequest(presentationRequest, credential) : - await this.handlePresentationRequest(presentationRequest, credential); - - const presentationAttachment = AttachmentDescriptor.build( - proof, - uuid(), - 'application/json', - undefined, - AttachmentFormats.ANONCREDS_PROOF - ); - const presentation = new Presentation( - { - comment: message.body.comment, - goalCode: message.body.goalCode - }, - [ - presentationAttachment, - ], - message.to, - message.from, - message.thid ?? message.id - ); - return presentation; - } - - - /** - * This method can be used by holders in order to disclose the value of a Credential - * JWT are just encoded plainText - * Anoncreds will really need to be disclosed as the fields are encoded. - * - * @param {Credential} credential - * @returns {AttributeType} - */ - async revealCredentialFields(credential: Credential, fields: string[], linkSecret: string) { - return this.pollux.revealCredentialFields(credential, fields, linkSecret); - } - - - /** - * match the Proof request to return relevant PresentationRequest. - * Proof Request comes from a Message Attachment. - * - * @param {AttachmentDescriptor} data - presentation proof request - * @returns {PresentationRequest} - * @throws - */ - private parseProofRequest(attachment: AttachmentDescriptor) { - const data = Message.Attachment.extractJSON(attachment); - if (attachment.format === AttachmentFormats.ANONCREDS_PROOF_REQUEST) { - return new PresentationRequest(AttachmentFormats.AnonCreds, data); - } - if (attachment.format === CredentialType.JWT) { - return new PresentationRequest(AttachmentFormats.JWT, data); - } - if (attachment.format === CredentialType.SDJWT) { - return new PresentationRequest(AttachmentFormats.SDJWT, data); - } - if (attachment.format === AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS) { - return new PresentationRequest(AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS, data); - } - throw new Error("Unsupported Proof Request"); - } - - private async handlePresentationDefinitionRequest( - request: PresentationRequest, - credential: Credential, - ): Promise { - if (request.isType(AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS)) { - if (credential instanceof JWTCredential) { - const privateKeys = await this.pluto.getDIDPrivateKeysByDID(DID.fromString(credential.subject)); - const privateKey = privateKeys.at(0); - if (!privateKey) { - throw new Error("Undefined privatekey from credential subject."); - } - if (!request.isType(AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS)) { - throw new Error("Undefined privatekey from credential subject."); - } - const presentationSubmission = await this.pollux.createPresentationSubmission( - request.toJSON(), - credential, - privateKey - ); - return JSON.stringify(presentationSubmission); - } - } - if (request.isType(AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS)) { - if (credential instanceof AnonCredsCredential) { - const storedLinkSecret = await this.pluto.getLinkSecret(); - if (!storedLinkSecret) { - throw new Error("Link secret not found."); - } - - const req = request.toJSON() - const presentationSubmission = await this.pollux.createPresentationSubmission( - req as any, - credential, - storedLinkSecret - ); - return JSON.stringify(presentationSubmission); - } - } - - throw new Error("Not implemented"); - } - - private async handlePresentationRequest( - request: PresentationRequest, - credential: Credential - ): Promise { - if (credential instanceof SDJWTCredential && request.isType(AttachmentFormats.SDJWT)) { - if (!credential.isProvable()) { - throw new Error("Credential is not Provable"); - } - const subjectDID = DID.from(credential.subject); - const prismPrivateKeys = await this.pluto.getDIDPrivateKeysByDID(subjectDID); - const prismPrivateKey = prismPrivateKeys.find((key) => key.curve === Curve.ED25519) - if (prismPrivateKey === undefined) { - throw new AgentError.CannotFindDIDPrivateKey(); - } - const signedJWT = await this.pollux.createPresentationProof(request, credential, { - did: subjectDID, - privateKey: prismPrivateKey - }); - return signedJWT; - } - if (credential instanceof AnonCredsCredential && request.isType(AttachmentFormats.AnonCreds)) { - const linkSecret = await this.pluto.getLinkSecret(); - if (!linkSecret) { - throw new AgentError.CannotFindLinkSecret(); - } - const presentation = await this.pollux.createPresentationProof(request, credential, { linkSecret }); - return presentation; - } - if (credential instanceof JWTCredential && request.isType(AttachmentFormats.JWT)) { - if (!credential.isProvable()) { - throw new Error("Credential is not Provable"); - } - const subjectDID = DID.from(credential.subject); - const prismPrivateKeys = await this.pluto.getDIDPrivateKeysByDID(subjectDID); - const prismPrivateKey = prismPrivateKeys.find((key) => key.curve === Curve.SECP256K1) - if (prismPrivateKey === undefined) { - throw new AgentError.CannotFindDIDPrivateKey(); - } - const signedJWT = await this.pollux.createPresentationProof(request, credential, { - did: subjectDID, - privateKey: prismPrivateKey - }); - return signedJWT; - } - - throw new AgentError.UnhandledPresentationRequest(); - } - - async isCredentialRevoked(credential: Credential): Promise { - return this.pollux.isCredentialRevoked(credential) - } - -} diff --git a/src/edge-agent/Agent.DIDHigherFunctions.ts b/src/edge-agent/Agent.DIDHigherFunctions.ts deleted file mode 100644 index 81c54e972..000000000 --- a/src/edge-agent/Agent.DIDHigherFunctions.ts +++ /dev/null @@ -1,201 +0,0 @@ -import { - Curve, - DID, - KeyProperties, - KeyTypes, - PublicKey, - Seed, - Service, - ServiceEndpoint, - Signature, -} from "../domain"; -import { Apollo } from "../domain/buildingBlocks/Apollo"; -import { Castor } from "../domain/buildingBlocks/Castor"; -import { Pluto } from "../domain/buildingBlocks/Pluto"; -import { AgentError } from "../domain/models/Errors"; -import { - AgentDIDHigherFunctions as AgentDIDHigherFunctionsClass, - MediatorHandler, -} from "./types"; -import { PrismKeyPathIndexTask } from "./Agent.PrismKeyPathIndexTask"; -import { PrismDerivationPath, PRISM_WALLET_PURPOSE, PRISM_DID_METHOD, AUTHENTICATION_KEY, ISSUING_KEY, PrismDerivationPathSchema } from "../domain/models/derivation/schemas/PrismDerivation"; - -/** - * An extension for the Edge agent that groups some DID related operations mainly used to expose the create did functionality - * - * @export - * @class AgentDIDHigherFunctions - * @typedef {AgentDIDHigherFunctions} - */ -export class AgentDIDHigherFunctions implements AgentDIDHigherFunctionsClass { - /** - * Creates an instance of AgentDIDHigherFunctions. - * - * @constructor - * @param {Apollo} apollo - * @param {Castor} castor - * @param {Pluto} pluto - * @param {ConnectionsManager} manager - * @param {MediatorHandler} mediationHandler - * @param {Seed} seed - */ - constructor( - protected apollo: Apollo, - protected castor: Castor, - protected pluto: Pluto, - protected mediationHandler: MediatorHandler, - protected seed: Seed - ) { } - - - /** - * Asyncronously sign with a DID - * - * @async - * @param {DID} did - * @param {Uint8Array} message - * @returns {Promise} - */ - async signWith(did: DID, message: Uint8Array): Promise { - const privateKeys = await this.pluto.getDIDPrivateKeysByDID(did); - - for (const privateKey of privateKeys) { - if (privateKey.isSignable()) { - return { - value: privateKey.sign(Buffer.from(message)), - }; - } - } - - throw new AgentError.CannotFindDIDPrivateKey(); - } - - /** - * Asyncronously create and store a new peer did - * - * @async - * @param {Service[]} services - * @param {boolean} [updateMediator=false] - * @returns {Promise} - */ - async createNewPeerDID( - services: Service[], - updateMediator = false - ): Promise { - const publicKeys: PublicKey[] = []; - const keyAgreementPrivateKey = this.apollo.createPrivateKey({ - type: KeyTypes.Curve25519, - curve: Curve.X25519, - }); - - const authenticationPrivateKey = this.apollo.createPrivateKey({ - type: KeyTypes.EC, - curve: Curve.ED25519, - }); - - publicKeys.push(keyAgreementPrivateKey.publicKey()); - publicKeys.push(authenticationPrivateKey.publicKey()); - const mediatorDID = this.mediationHandler.mediator?.mediatorDID; - - if ( - mediatorDID && - !services.find((service) => { - return service.isDIDCommMessaging; - }) - ) { - //TODO(): This still needs to be done update the key List - services.push( - new Service( - "#didcomm-1", - ["DIDCommMessaging"], - new ServiceEndpoint(mediatorDID.toString()) - ) - ); - } - const did = await this.castor.createPeerDID(publicKeys, services); - - if (updateMediator) { - await this.mediationHandler.updateKeyListWithDIDs([did]); - } - - await this.pluto.storeDID(did, [ - keyAgreementPrivateKey, - authenticationPrivateKey, - ]); - - return did; - } - - /** - * Asyncronously create and store a PrismDID - * - * @async - * @param {string} alias - * @param {Service[]} services - * @param {?number} [keyPathIndex] - * @returns {Promise} - */ - async createNewPrismDID( - alias: string, - services: Service[], - keyPathIndex?: number - ): Promise { - const authenticationDerivation = new PrismDerivationPath([ - PRISM_WALLET_PURPOSE, - PRISM_DID_METHOD, - 0, - AUTHENTICATION_KEY, - await this.getNextKeyPathIndex(keyPathIndex) - ]); - const issuingDerivation = new PrismDerivationPath([ - PRISM_WALLET_PURPOSE, - PRISM_DID_METHOD, - 0, - ISSUING_KEY, - await this.getNextKeyPathIndex(keyPathIndex) - ]); - - const sk = this.apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, - [KeyProperties.curve]: Curve.SECP256K1, - [KeyProperties.seed]: Buffer.from(this.seed.value).toString("hex"), - [KeyProperties.derivationPath]: authenticationDerivation.toString(), - [KeyProperties.derivationSchema]: PrismDerivationPathSchema - }); - const edSk = this.apollo.createPrivateKey({ - [KeyProperties.type]: KeyTypes.EC, - [KeyProperties.curve]: Curve.ED25519, - [KeyProperties.seed]: Buffer.from(this.seed.value).toString("hex"), - [KeyProperties.derivationPath]: issuingDerivation.toString(), - [KeyProperties.derivationSchema]: PrismDerivationPathSchema - }); - - const publicKey = sk.publicKey(); - const did = await this.castor.createPrismDID( - publicKey, - services, - [ - edSk.publicKey() - ] - ); - await this.pluto.storeDID(did, [sk, edSk], alias); - - return did; - } - - /** - * Determine the Index for the subsequent Key - * - * @returns {number} - */ - private async getNextKeyPathIndex( - optionalKeyIndex?: number - ): Promise { - const getIndexTask = new PrismKeyPathIndexTask(this.pluto); - const index = await getIndexTask.run( - optionalKeyIndex - ); - const next = index; - return next; - } -} diff --git a/src/edge-agent/Agent.Invitations.ts b/src/edge-agent/Agent.Invitations.ts deleted file mode 100644 index 194beabc1..000000000 --- a/src/edge-agent/Agent.Invitations.ts +++ /dev/null @@ -1,195 +0,0 @@ -import { OutOfBandInvitation } from "./protocols/invitation/v2/OutOfBandInvitation"; -import { - AgentDIDHigherFunctions, - AgentInvitations as AgentInvitationsClass, - InvitationType, - PrismOnboardingInvitation, -} from "./types"; -import { - Service as DIDDocumentService, - ServiceEndpoint as DIDDocumentServiceEndpoint, - Message, -} from "../domain"; -import { AgentError } from "../domain/models/Errors"; -import { - findProtocolTypeByValue, - ProtocolType, -} from "./protocols/ProtocolTypes"; -import { Api } from "../domain/models/Api"; -import { ConnectionsManager } from "./connectionsManager/ConnectionsManager"; -import { DIDCommConnectionRunner } from "./protocols/connection/DIDCommConnectionRunner"; -import { Pluto } from "../domain/buildingBlocks/Pluto"; -import { DIDCommInvitationRunner } from "./protocols/invitation/v2/DIDCommInvitationRunner"; - -/** - * An extension for the Edge agent that groups the functionality to parse, manage and - * respond to Cloud Agent onboarding and didcomm v2 invitations - * - * @export - * @class AgentInvitations - * @typedef {AgentInvitations} - */ -export class AgentInvitations implements AgentInvitationsClass { - /** - * Creates an instance of AgentInvitations. - * - * @constructor - * @param {Pluto} pluto - * @param {Api} api - * @param {AgentDIDHigherFunctions} agentDIDHigherFunctions - * @param {ConnectionsManager} connection - */ - constructor( - private pluto: Pluto, - private api: Api, - private agentDIDHigherFunctions: AgentDIDHigherFunctions, - private connection: ConnectionsManager - ) { } - - /** - * Asyncronously parse an invitation from a valid json string - * - * @async - * @param {string} str - * @returns {Promise} - */ - async parseInvitation(str: string): Promise { - const json = JSON.parse(str); - const typeString = findProtocolTypeByValue(json.type); - - switch (typeString) { - case ProtocolType.PrismOnboarding: - return this.parsePrismInvitation(str); - case ProtocolType.Didcomminvitation: - return this.parseOOBInvitation(new URL(str)); - } - - throw new AgentError.UnknownInvitationTypeError(); - } - - async acceptInvitation(invitation: InvitationType, optionalAlias?: string): Promise { - if (invitation.type === ProtocolType.Didcomminvitation) { - return this.acceptDIDCommInvitation(invitation, optionalAlias); - } - - if (invitation instanceof PrismOnboardingInvitation) { - return this.acceptPrismOnboardingInvitation(invitation); - } - - throw new AgentError.InvitationIsInvalidError(); - } - - /** - * Asyncronously accept a didcomm v2 invitation, will create a pair between the Agent - * its connecting with and the current owner's did - * - * @async - * @param {OutOfBandInvitation} invitation - * @returns {*} - */ - async acceptDIDCommInvitation(invitation: OutOfBandInvitation, optionalAlias?: string) { - if (!this.connection.mediationHandler.mediator) { - throw new AgentError.NoMediatorAvailableError(); - } - const [attachment] = invitation.attachments ?? []; - if (!attachment) { - const ownDID = await this.agentDIDHigherFunctions.createNewPeerDID( - [], - true - ); - const pair = await new DIDCommConnectionRunner( - invitation, - this.pluto, - ownDID, - this.connection, - optionalAlias - ).run(); - await this.connection.addConnection(pair); - } else { - const ownDID = await this.agentDIDHigherFunctions.createNewPeerDID( - [], - true - ); - const msg = Message.fromJson({ - ...attachment.payload, - to: ownDID.toString() - }); - await this.pluto.storeMessage(msg) - } - } - - /** - * Asyncronously accept an onboarding invitation, used to onboard the current DID in the Cloud Agent. - * - * @async - * @param {PrismOnboardingInvitation} invitation - * @returns {Promise} - */ - async acceptPrismOnboardingInvitation(invitation: PrismOnboardingInvitation): Promise { - if (!invitation.from) { - throw new AgentError.UnknownInvitationTypeError(); - } - interface SendDID { - did: string; - } - const body: SendDID = { - did: invitation.from.toString(), - }; - const response = await this.api.request( - "POST", - invitation.onboardEndpoint, - new Map(), - new Map(), - body - ); - if (response.httpStatus != 200) { - throw new AgentError.FailedToOnboardError(); - } - } - - /** - * Asyncronously parse a prismOnboarding invitation from a string - * - * @async - * @param {string} str - * @returns {Promise} - */ - async parsePrismInvitation(str: string): Promise { - try { - const prismOnboarding = - OutOfBandInvitation.parsePrismOnboardingInvitationFromJson(str); - const url = prismOnboarding.onboardEndpoint; - const services: DIDDocumentService[] = [ - new DIDDocumentService( - "#didcomm-1", - ["DIDCommMessaging"], - new DIDDocumentServiceEndpoint(url, ["DIDCommMessaging"]) - ), - ]; - const updateMediator = true; - const did = await this.agentDIDHigherFunctions.createNewPeerDID( - services, - updateMediator - ); - prismOnboarding.from = did; - return prismOnboarding; - } catch (e) { - if (e instanceof Error) { - throw new AgentError.UnknownInvitationTypeError(e.message); - } else { - throw e; - } - } - } - - /** - * Asyncronously parse an out of band invitation from a URI as the oob come in format of valid URL - * - * @async - * @param {URL} str - * @returns {Promise} - */ - async parseOOBInvitation(str: URL): Promise { - return new DIDCommInvitationRunner(str).run(); - } -} diff --git a/src/edge-agent/Agent.ts b/src/edge-agent/Agent.ts index 6bf2a4e35..84bafec11 100644 --- a/src/edge-agent/Agent.ts +++ b/src/edge-agent/Agent.ts @@ -1,35 +1,12 @@ import * as Domain from "../domain"; import Apollo from "../apollo"; import Castor from "../castor"; -import Mercury from "../mercury"; import Pollux from "../pollux"; -import { FetchApi } from "./helpers/FetchApi"; -import { - AgentCredentials as AgentCredentialsClass, - AgentDIDHigherFunctions as AgentDIDHigherFunctionsClass, - AgentInvitations as AgentInvitationsClass, - AgentOptions, - EventCallback, - InvitationType, - ListenerKey, - MediatorHandler, - PrismOnboardingInvitation, -} from "./types"; - import { AgentBackup } from "./Agent.Backup"; -import { AgentCredentials } from "./Agent.Credentials"; -import { AgentDIDHigherFunctions } from "./Agent.DIDHigherFunctions"; -import { AgentInvitations } from "./Agent.Invitations"; -import { ConnectionsManager } from "./connectionsManager/ConnectionsManager"; -import { OutOfBandInvitation } from "./protocols/invitation/v2/OutOfBandInvitation"; -import { OfferCredential } from "./protocols/issueCredential/OfferCredential"; -import { RequestCredential } from "./protocols/issueCredential/RequestCredential"; -import { IssueCredential } from "./protocols/issueCredential/IssueCredential"; -import { Presentation } from "./protocols/proofPresentation/Presentation"; -import { RequestPresentation } from "./protocols/proofPresentation/RequestPresentation"; -import { DIDCommWrapper } from "../mercury/didcomm/Wrapper"; -import { PublicMediatorStore } from "./mediator/PlutoMediatorStore"; -import { BasicMediatorHandler } from "./mediator/BasicMediatorHandler"; +import { SignWithDID } from "./didFunctions/Sign"; +import { CreatePrismDID } from "./didFunctions/CreatePrismDID"; +import { FetchApi } from "./helpers/FetchApi"; +import { Task } from "../utils/tasks"; enum AgentState { STOPPED = "stopped", @@ -45,11 +22,7 @@ enum AgentState { * @class Agent * @typedef {Agent} */ -export default class Agent - implements - AgentCredentialsClass, - AgentDIDHigherFunctionsClass, - AgentInvitationsClass { +export default class Agent { /** * Agent state * @@ -58,14 +31,8 @@ export default class Agent */ public state: AgentState = AgentState.STOPPED; public backup: AgentBackup; - - private agentCredentials: AgentCredentials; - private agentDIDHigherFunctions: AgentDIDHigherFunctions; - private agentInvitations: AgentInvitations; - public readonly pollux: Pollux; - /** * Creates an instance of Agent. * @@ -73,205 +40,49 @@ export default class Agent * @param {Apollo} apollo * @param {Castor} castor * @param {Pluto} pluto - * @param {Mercury} mercury - * @param {MediatorHandler} mediationHandler - * @param {ConnectionsManager} connectionManager * @param {Seed} [seed=apollo.createRandomSeed().seed] - * @param {Api} [api=new ApiImpl()] + * @param {Api} [api=new FetchApi()] */ constructor( public readonly apollo: Domain.Apollo, 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 ) { - this.pollux = new Pollux(apollo, castor); - this.agentDIDHigherFunctions = new AgentDIDHigherFunctions( - apollo, - castor, - pluto, - mediationHandler, - seed - ); - - this.agentCredentials = new AgentCredentials( - apollo, - castor, - pluto, - this.pollux, - seed, - mercury, - this.agentDIDHigherFunctions - ); - - this.connectionManager = - connectionManager || - new ConnectionsManager( - castor, - mercury, - pluto, - this.agentCredentials, - mediationHandler, - [], - options - ); - - this.agentInvitations = new AgentInvitations( - this.pluto, - this.api, - this.agentDIDHigherFunctions, - this.connectionManager - ); - this.backup = new AgentBackup(this); } - isCredentialRevoked(credential: Domain.Credential) { - return this.agentCredentials.isCredentialRevoked(credential); - } - /** * Convenience initializer for Agent * allowing default instantiation, omitting all but the absolute necessary parameters * * @param {Object} params - dependencies object - * @param {DID | string} params.mediatorDID - did of the mediator to be used * @param {Pluto} params.pluto - storage implementation * @param {Api} [params.api] * @param {Apollo} [params.apollo] * @param {Castor} [params.castor] - * @param {Mercury} [params.mercury] * @param {Seed} [params.seed] * @returns {Agent} */ static initialize(params: { - mediatorDID: Domain.DID | string; pluto: Domain.Pluto; api?: Domain.Api; apollo?: Domain.Apollo; castor?: Domain.Castor; - mercury?: Domain.Mercury; seed?: Domain.Seed; - options?: AgentOptions; }): Agent { - const mediatorDID = Domain.DID.from(params.mediatorDID); const pluto = params.pluto; - const api = params.api ?? new FetchApi(); const apollo = params.apollo ?? new Apollo(); 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 pollux = new Pollux(apollo, castor); const seed = params.seed ?? apollo.createRandomSeed().seed; - - const agentCredentials = new AgentCredentials( - apollo, - castor, - pluto, - pollux, - seed, - mercury, - new AgentDIDHigherFunctions( - apollo, - castor, - pluto, - handler, - seed - ) - ); - - const manager = new ConnectionsManager(castor, mercury, pluto, agentCredentials, handler, [], params.options); - - const agent = new Agent( - apollo, - castor, - pluto, - mercury, - handler, - manager, - seed, - api, - params.options - ); + const agent = new Agent(apollo, castor, pluto, seed, api); return agent; } - /** - * Get current mediator DID if available or null - * - * @public - * @readonly - * @type {DID} - */ - public get currentMediatorDID() { - return this.mediationHandler.mediator?.mediatorDID; - } - - /** - * Mainly for testing purposes but instantiating the Agent from a ConnectionManager directly - * - * @static - * @param {Apollo} apollo - * @param {Castor} castor - * @param {Pluto} pluto - * @param {Mercury} mercury - * @param {ConnectionsManager} connectionManager - * @param {?Seed} [seed] - * @param {?Api} [api] - * @returns {Agent} - */ - static instanceFromConnectionManager( - apollo: Domain.Apollo, - castor: Domain.Castor, - pluto: Domain.Pluto, - mercury: Domain.Mercury, - connectionManager: ConnectionsManager, - seed?: Domain.Seed, - api?: Domain.Api, - options?: AgentOptions - ) { - return new Agent( - apollo, - castor, - pluto, - mercury, - connectionManager.mediationHandler, - connectionManager, - seed ? seed : apollo.createRandomSeed().seed, - api ? api : new FetchApi(), - options - ); - } - - /** - * This method can be used by holders in order to disclose the value of a Credential - * JWT are just encoded plainText - * Anoncreds will really need to be disclosed as the fields are encoded. - * - * @param {Credential} credential - * @returns {AttributeType} - */ - async revealCredentialFields(credential: Domain.Credential, fields: string[], linkSecret: string) { - return this.agentCredentials.revealCredentialFields( - credential, - fields, - linkSecret - ); - } - - /** * Asyncronously start the agent * @@ -279,41 +90,10 @@ export default class Agent * @returns {Promise} */ async start(): Promise { - if (this.state !== AgentState.STOPPED) { - return this.state; - } - this.state = AgentState.STARTING; - try { - + if (this.state === AgentState.STOPPED) { + this.state = AgentState.STARTING; await this.pluto.start(); - await this.pollux.start(); - - await this.connectionManager.startMediator(); - - } catch (e) { - if (e instanceof Domain.AgentError.NoMediatorAvailableError) { - const hostDID = await this.createNewPeerDID([], false); - - await this.connectionManager.registerMediator(hostDID); - - } else throw e; - } - - if (this.connectionManager.mediationHandler.mediator !== undefined) { - await this.connectionManager.startFetchingMessages(5); - this.state = AgentState.RUNNING; - - } else { - throw new Domain.AgentError.MediationRequestFailedError("Mediation failed"); - - } - - const storedLinkSecret = await this.pluto.getLinkSecret(); - if (storedLinkSecret == null) { - const secret = this.pollux.anoncreds.createLinksecret(); - const linkSecret = new Domain.LinkSecret(secret); - await this.pluto.storeLinkSecret(linkSecret); } return this.state; @@ -329,12 +109,38 @@ export default class Agent if (this.state !== AgentState.RUNNING) { return; } - this.state = AgentState.STOPPING; - this.connectionManager.stopAllEvents(); - this.connectionManager.stopFetchingMessages(); this.state = AgentState.STOPPED; } + /** + * This method can be used by holders in order to disclose the value of a Credential + * JWT are just encoded plainText + * Anoncreds will really need to be disclosed as the fields are encoded. + * + * @param {Credential} credential + * @returns {AttributeType} + */ + async revealCredentialFields(credential: Domain.Credential, fields: string[], linkSecret: string) { + return this.pollux.revealCredentialFields(credential, fields, linkSecret); + } + + isCredentialRevoked(credential: Domain.Credential) { + return this.pollux.isCredentialRevoked(credential); + } + + private runTask(task: Task) { + const ctx = new Task.Context({ + Api: this.api, + Apollo: this.apollo, + Castor: this.castor, + Pluto: this.pluto, + Pollux: this.pollux, + Seed: this.seed, + }); + + return ctx.run(task); + } + /** * Asyncronously create a new PrismDID * @@ -349,51 +155,8 @@ export default class Agent services: Domain.Service[] = [], keyPathIndex?: number ): Promise { - return this.agentDIDHigherFunctions.createNewPrismDID( - alias, - services, - keyPathIndex - ); - } - - /** - * Asyncronously Create a new PeerDID - * - * @async - * @param {DIDDocumentService[]} [services=[]] - * @param {boolean} [updateMediator=true] - * @returns {Promise} - */ - async createNewPeerDID( - services: Domain.Service[] = [], - updateMediator = true - ): Promise { - return this.agentDIDHigherFunctions.createNewPeerDID( - services, - updateMediator - ); - } - - /** - * Asyncronously parse an invitation from a valid json string - * - * @async - * @param {string} str - * @returns {Promise} - */ - async parseInvitation(str: string): Promise { - return this.agentInvitations.parseInvitation(str); - } - - /** - * Handle an invitation to create a connection - * - * @async - * @param {InvitationType} invitation - an OOB or PrismOnboarding invitation - * @returns {Promise} - */ - async acceptInvitation(invitation: InvitationType, optionalAlias?: string): Promise { - return this.agentInvitations.acceptInvitation(invitation, optionalAlias); + const task = new CreatePrismDID({ alias, services, keyPathIndex }); + return this.runTask(task); } /** @@ -405,70 +168,8 @@ export default class Agent * @returns {Promise} */ async signWith(did: Domain.DID, message: Uint8Array): Promise { - return this.agentDIDHigherFunctions.signWith(did, message); - } - - /** - * Asyncronously parse a prismOnboarding invitation from a string - * - * @async - * @param {string} str - * @returns {Promise} - */ - async parsePrismInvitation(str: string): Promise { - return this.agentInvitations.parsePrismInvitation(str); - } - - /** - * Asyncronously parse an out of band invitation from a URI as the oob come in format of valid URL - * - * @async - * @param {URL} str - * @returns {Promise} - */ - async parseOOBInvitation(str: URL): Promise { - return this.agentInvitations.parseOOBInvitation(str); - } - - /** - * Asyncronously accept a didcomm v2 invitation, will create a pair between the Agent - * its connecting with and the current owner's did - * - * @deprecated - use `acceptInvitation` - * @async - * @param {OutOfBandInvitation} invitation - * @returns {*} - */ - async acceptDIDCommInvitation( - invitation: OutOfBandInvitation, optionalAlias?: string - ): Promise { - return this.agentInvitations.acceptDIDCommInvitation(invitation, optionalAlias); - } - - /** - * Start fetching for new messages in such way that it can be stopped at any point in time without causing memory leaks - * - * @param {number} iterationPeriod - */ - async startFetchingMessages(iterationPeriod: number): Promise { - return this.connectionManager.startFetchingMessages(iterationPeriod); - } - - /** - * Stops fetching messages - */ - stopFetchingMessages(): void { - this.connectionManager.stopFetchingMessages(); - } - - /** - * Asyncronously send a didcomm Message - * - * @param {Message} message - * @returns {Promise} - */ - sendMessage(message: Domain.Message): Promise { - return this.connectionManager.sendMessage(message); + const task = new SignWithDID({ did, message }); + return this.runTask(task); } /** @@ -477,120 +178,6 @@ export default class Agent * @returns {Promise} */ verifiableCredentials(): Promise { - return this.agentCredentials.verifiableCredentials(); - } - - /** - * Add an event listener to get notified from an Event "MESSAGE" - * - * @param {ListenerKey} eventName - * @param {EventCallback} callback - */ - addListener(eventName: ListenerKey, callback: EventCallback): void { - return this.connectionManager.events.addListener(eventName, callback); - } - - /** - * Remove event listener, used by stop procedure - * @date 20/06/2023 - 14:31:30 - * - * @param {ListenerKey} eventName - * @param {EventCallback} callback - */ - removeListener(eventName: ListenerKey, callback: EventCallback): void { - return this.connectionManager.events.removeListener(eventName, callback); + return this.pluto.getAllCredentials(); } - - /** - * Asyncronously prepare a request credential message from a valid offerCredential for now supporting w3c verifiable credentials offers. - * - * @async - * @param {OfferCredential} offer - * @returns {Promise} - */ - async prepareRequestCredentialWithIssuer( - offer: OfferCredential - ): Promise { - return this.agentCredentials.prepareRequestCredentialWithIssuer(offer); - } - - /** - * Extract the verifiableCredential object from the Issue credential message asyncronously - * - * @async - * @param {IssueCredential} message - * @returns {Promise} - */ - async processIssuedCredentialMessage( - message: IssueCredential - ): Promise { - return this.agentCredentials.processIssuedCredentialMessage(message); - } - - /** - * Asyncronously create a verifiablePresentation from a valid stored verifiableCredential - * This is used when the verified requests a specific verifiable credential, this will create the actual - * instance of the presentation which we can share with the verifier. - * - * @async - * @param {RequestPresentation} request - * @param {VerifiableCredential} credential - * @returns {Promise} - */ - async createPresentationForRequestProof( - request: RequestPresentation, - credential: Domain.Credential - ): Promise { - return this.agentCredentials.createPresentationForRequestProof( - request, - credential - ); - } - - - /** - * Initiate a PresentationRequest from the SDK, to create oob Verification Requests - * @param {Domain.CredentialType} type - * @param {Domain.DID} toDID - * @param {ProofTypes[]} proofTypes[] - * @returns - * - * 1. Example use-case: Send a Presentation Request for a JWT credential issued by a specific issuer - * ```ts - * agent.initiatePresentationRequest( - * Domain.CredentialType.JWT, - * toDID, - * { issuer: Domain.DID.fromString("did:peer:12345"), claims: {}} - * ); - * ``` - * - * 2. Example use-case: Send a Presentation Request for a JWT credential issued by a specific issuer and specific claims - * ```ts - * agent.initiatePresentationRequest( - * Domain.CredentialType.JWT, - * toDID, - * { issuer: Domain.DID.fromString("did:peer:12345"), claims: {email: {type: 'string', pattern:'email@email.com'}}} - * ); - * ``` - */ - async initiatePresentationRequest(type: T, toDID: Domain.DID, presentationClaims: Domain.PresentationClaims): Promise { - const requestPresentation = await this.agentCredentials.initiatePresentationRequest( - type, - toDID, - presentationClaims - ); - - const requestPresentationMessage = requestPresentation.makeMessage(); - await this.connectionManager.sendMessage(requestPresentationMessage); - return requestPresentation; - } - - /** - * Initiate the Presentation and presentationSubmission - * @param presentation - */ - async handlePresentation(presentation: Presentation): Promise { - return this.agentCredentials.handlePresentation(presentation); - } - } diff --git a/src/edge-agent/connectionsManager/ConnectionsManager.ts b/src/edge-agent/connectionsManager/ConnectionsManager.ts index 8747ce5b2..24a88b32c 100644 --- a/src/edge-agent/connectionsManager/ConnectionsManager.ts +++ b/src/edge-agent/connectionsManager/ConnectionsManager.ts @@ -1,4 +1,4 @@ -import { DID, Message, MessageDirection } from "../../domain"; +import { DID, Message, MessageDirection, Pollux } from "../../domain"; import { Castor } from "../../domain/buildingBlocks/Castor"; import { Mercury } from "../../domain/buildingBlocks/Mercury"; import { Pluto } from "../../domain/buildingBlocks/Pluto"; @@ -7,7 +7,6 @@ import { AgentError } from "../../domain/models/Errors"; import { AgentMessageEvents } from "../Agent.MessageEvents"; import { CancellableTask } from "../helpers/Task"; import { - AgentCredentials, AgentMessageEvents as AgentMessageEventsClass, AgentOptions, ConnectionsManager as ConnectionsManagerClass, @@ -17,6 +16,8 @@ import { 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"; /** @@ -70,7 +71,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { public castor: Castor, public mercury: Mercury, public pluto: Pluto, - public agentCredentials: AgentCredentials, + public pollux: Pollux, public mediationHandler: MediatorHandler, public pairings: DIDPair[] = [], public options?: AgentOptions @@ -79,7 +80,7 @@ export class ConnectionsManager implements ConnectionsManagerClass { } get withWebsocketsExperiment() { - return this.options?.experiments?.liveMode === true + return this.options?.experiments?.liveMode === true; } /** @@ -159,10 +160,11 @@ export class ConnectionsManager implements ConnectionsManagerClass { if (matchingMessages.length > 0) { for (const message of matchingMessages) { - const issueMessage = IssueCredential.fromMessage(message); - const credential = await this.agentCredentials.processIssuedCredentialMessage( - issueMessage - ); + const issueCredential = IssueCredential.fromMessage(message); + const ctx = new Task.Context({ Pluto: this.pluto, Pollux: this.pollux }); + const task = new HandleIssueCredential({ issueCredential }); + const credential = await ctx.run(task); + await this.pluto.revokeCredential(credential); this.events.emit(ListenerKey.REVOKE, credential); } diff --git a/src/edge-agent/didFunctions/CreateJwt.ts b/src/edge-agent/didFunctions/CreateJwt.ts new file mode 100644 index 000000000..32ae6d209 --- /dev/null +++ b/src/edge-agent/didFunctions/CreateJwt.ts @@ -0,0 +1,63 @@ +import { base58btc } from "multiformats/bases/base58"; +import * as Domain from "../../domain"; +import { expect } from "../../utils"; +import { Task } from "../../utils/tasks"; + +/** + * Asyncronously sign with a DID + * + * @async + * @param {DID} did + * @param payload + * @param header + * @returns {string} + */ + +interface Args { + did: Domain.DID; + payload: Partial; + header?: Partial; +} + +export class CreateJWT extends Task { + async run(ctx: Task.Context) { + const keys = await ctx.Pluto.getDIDPrivateKeysByDID(this.args.did); + const secpKey = expect( + keys.find(x => x.curve === Domain.Curve.SECP256K1), + "key not found" + ); + + const kid = await this.getSigningKid(ctx, this.args.did, secpKey); + + const jwt = await Domain.JWT.sign( + this.args.did, + secpKey, + this.args.payload, + { ...this.args.header, kid } + ); + + return jwt; + } + + /** + * try to match the privateKey with a dids verificationMethod + * returning the relevant key id + * + * @param did + * @param privateKey + * @returns {string} kid (key identifier) + */ + private async getSigningKid(ctx: Task.Context, did: Domain.DID, privateKey: Domain.PrivateKey) { + const pubKey = privateKey.publicKey(); + const encoded = base58btc.encode(pubKey.to.Buffer()); + const document = await ctx.Castor.resolveDID(did.toString()); + console.dir({ document }, { depth: null }); + + const signingKey = document.verificationMethods.find(key => { + // TODO improve key identification + return key.publicKeyMultibase === encoded && key.id.includes("#authentication"); + }); + + return signingKey?.id; + } +} diff --git a/src/edge-agent/didFunctions/CreatePrismDID.ts b/src/edge-agent/didFunctions/CreatePrismDID.ts new file mode 100644 index 000000000..0a45be094 --- /dev/null +++ b/src/edge-agent/didFunctions/CreatePrismDID.ts @@ -0,0 +1,76 @@ +import * as Domain from "../../domain"; +import { + PrismDerivationPath, + PRISM_WALLET_PURPOSE, + PRISM_DID_METHOD, + AUTHENTICATION_KEY, + ISSUING_KEY, + PrismDerivationPathSchema +} from "../../domain/models/derivation/schemas/PrismDerivation"; +import { Task } from "../../utils/tasks"; +import { PrismKeyPathIndexTask } from "./PrismKeyPathIndex"; + +/** + * Handle the creation of a PrismDID + * + * Calculate and use the latest Prism DID KeyPathIndex. + * Create the relevant PrivateKeys. + * Store the PrismDID plus Keys in Pluto + */ + +interface Args { + alias: string; + services: Domain.Service[]; + keyPathIndex?: number; +} + +export class CreatePrismDID extends Task { + async run(ctx: Task.Context) { + const getIndexTask = new PrismKeyPathIndexTask({ index: this.args.keyPathIndex }); + const index = await ctx.run(getIndexTask); + + const authenticationDerivation = new PrismDerivationPath([ + PRISM_WALLET_PURPOSE, + PRISM_DID_METHOD, + 0, + AUTHENTICATION_KEY, + index + ]); + + const issuingDerivation = new PrismDerivationPath([ + PRISM_WALLET_PURPOSE, + PRISM_DID_METHOD, + 0, + ISSUING_KEY, + index + ]); + + const seedHex = Buffer.from(ctx.Seed.value).toString("hex"); + + const sk = ctx.Apollo.createPrivateKey({ + [Domain.KeyProperties.type]: Domain.KeyTypes.EC, + [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, + [Domain.KeyProperties.seed]: seedHex, + [Domain.KeyProperties.derivationPath]: authenticationDerivation.toString(), + [Domain.KeyProperties.derivationSchema]: PrismDerivationPathSchema + }); + + const edSk = ctx.Apollo.createPrivateKey({ + [Domain.KeyProperties.type]: Domain.KeyTypes.EC, + [Domain.KeyProperties.curve]: Domain.Curve.ED25519, + [Domain.KeyProperties.seed]: seedHex, + [Domain.KeyProperties.derivationPath]: issuingDerivation.toString(), + [Domain.KeyProperties.derivationSchema]: PrismDerivationPathSchema + }); + + const did = await ctx.Castor.createPrismDID( + sk.publicKey(), + this.args.services, + [edSk.publicKey()] + ); + + await ctx.Pluto.storeDID(did, [sk, edSk], this.args.alias); + + return did; + } +} diff --git a/src/edge-agent/Agent.PrismKeyPathIndexTask.ts b/src/edge-agent/didFunctions/PrismKeyPathIndex.ts similarity index 56% rename from src/edge-agent/Agent.PrismKeyPathIndexTask.ts rename to src/edge-agent/didFunctions/PrismKeyPathIndex.ts index e5346f42e..eeac22095 100644 --- a/src/edge-agent/Agent.PrismKeyPathIndexTask.ts +++ b/src/edge-agent/didFunctions/PrismKeyPathIndex.ts @@ -1,4 +1,4 @@ -import type * as Domain from "../domain"; +import { Task } from "../../utils/tasks"; /** * Task to find the latest Prism DID KeyPathIndex @@ -8,19 +8,23 @@ import type * as Domain from "../domain"; * * @returns number */ -export class PrismKeyPathIndexTask { - constructor(private readonly pluto: Domain.Pluto) { } - async run( - index?: number - ): Promise { - const prismDIDs = await this.pluto.getAllPrismDIDs(); +interface Args { + index?: number; +} + +export class PrismKeyPathIndexTask extends Task { + async run(ctx: Task.Context) { + const { index } = this.args; + const prismDIDs = await ctx.Pluto.getAllPrismDIDs(); + if (prismDIDs.length <= 0) { - return 0 + return 0; } + const indexes = prismDIDs.map(x => x.privateKey.index ?? 0); const maxKey = Math.max(0, ...indexes); const keyPathIndex = maxKey; - return typeof index !== 'undefined' && index > keyPathIndex ? index : keyPathIndex + 1 + return typeof index !== 'undefined' && index > keyPathIndex ? index : keyPathIndex + 1; } } diff --git a/src/edge-agent/didFunctions/Sign.ts b/src/edge-agent/didFunctions/Sign.ts new file mode 100644 index 000000000..b87cd7f5b --- /dev/null +++ b/src/edge-agent/didFunctions/Sign.ts @@ -0,0 +1,32 @@ +import * as Domain from "../../domain"; +import { Task } from "../../utils/tasks"; + +/** + * Asyncronously sign with a DID + * + * @async + * @param {DID} did + * @param {Uint8Array} message + * @returns {Promise} + */ + +interface Args { + did: Domain.DID; + message: Uint8Array; +} + +export class SignWithDID extends Task { + async run(ctx: Task.Context) { + const privateKeys = await ctx.Pluto.getDIDPrivateKeysByDID(this.args.did); + + for (const privateKey of privateKeys) { + if (privateKey.isSignable()) { + return { + value: privateKey.sign(Buffer.from(this.args.message)), + }; + } + } + + throw new Domain.AgentError.CannotFindDIDPrivateKey(); + } +} diff --git a/src/edge-agent/didFunctions/index.ts b/src/edge-agent/didFunctions/index.ts new file mode 100644 index 000000000..81ca9ef6f --- /dev/null +++ b/src/edge-agent/didFunctions/index.ts @@ -0,0 +1,3 @@ +export * from "./CreatePrismDID"; +export * from "./PrismKeyPathIndex"; +export * from "./Sign"; diff --git a/src/edge-agent/didcomm/Agent.ts b/src/edge-agent/didcomm/Agent.ts new file mode 100644 index 000000000..977e15702 --- /dev/null +++ b/src/edge-agent/didcomm/Agent.ts @@ -0,0 +1,599 @@ +import * as Domain from "../../domain"; +import Mercury from "../../mercury"; +import { + AgentOptions, + EventCallback, + InvitationType, + ListenerKey, + PrismOnboardingInvitation, + MediatorHandler, +} from "../types"; + +import { AgentBackup } from "../Agent.Backup"; +import { ConnectionsManager } from "../connectionsManager/ConnectionsManager"; +import { OutOfBandInvitation } from "../protocols/invitation/v2/OutOfBandInvitation"; +import { OfferCredential } from "../protocols/issueCredential/OfferCredential"; +import { RequestCredential } from "../protocols/issueCredential/RequestCredential"; +import { IssueCredential } from "../protocols/issueCredential/IssueCredential"; +import { Presentation } from "../protocols/proofPresentation/Presentation"; +import { RequestPresentation } from "../protocols/proofPresentation/RequestPresentation"; +import { DIDCommWrapper } from "../../mercury/didcomm/Wrapper"; +import { PublicMediatorStore } from "../mediator/PlutoMediatorStore"; +import { BasicMediatorHandler } from "../mediator/BasicMediatorHandler"; +import { CreatePeerDID } from "./CreatePeerDID"; +import { CreatePresentationRequest } from "./CreatePresentationRequest"; +import { HandleIssueCredential } from "./HandleIssueCredential"; +import { HandleOfferCredential } from "./HandleOfferCredential"; +import { HandlePresentation } from "./HandlePresentation"; +import { CreatePresentation } from "./CreatePresentation"; +import { ProtocolType, findProtocolTypeByValue } from "../protocols/ProtocolTypes"; +import { DIDCommConnectionRunner } from "../protocols/connection/DIDCommConnectionRunner"; +import { DIDCommInvitationRunner } from "../protocols/invitation/v2/DIDCommInvitationRunner"; +import Pollux from "../../pollux"; +import Apollo from "../../apollo"; +import Castor from "../../castor"; +import * as DIDfns from "../didFunctions"; +import { Task } from "../../utils/tasks"; +import { DIDCommContext } from "./Context"; +import { FetchApi } from "../helpers/FetchApi"; +import { isNil } from "../../utils"; + +enum AgentState { + STOPPED = "stopped", + STARTING = "starting", + RUNNING = "running", + STOPPING = "stopping", +} + +/** + * Edge agent implementation + * + * @export + * @class Agent + * @typedef {Agent} + */ +export default class DIDCommAgent { + /** + * Agent state + * + * @public + * @type {AgentState} + */ + public state: AgentState = AgentState.STOPPED; + public backup: AgentBackup; + public readonly pollux: Pollux; + + + /** + * Creates an instance of Agent. + * + * @constructor + * @param {MediatorHandler} mediationHandler + * @param {ConnectionsManager} connectionManager + */ + constructor( + public readonly apollo: Domain.Apollo, + 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 + ) { + this.pollux = new Pollux(apollo, castor); + this.backup = new AgentBackup(this); + } + + /** + * Convenience initializer for Agent + * allowing default instantiation, omitting all but the absolute necessary parameters + * + * @param {Object} params - dependencies object + * @param {DID | string} params.mediatorDID - did of the mediator to be used + * @param {Pluto} params.pluto - storage implementation + * @param {Api} [params.api] + * @param {Apollo} [params.apollo] + * @param {Castor} [params.castor] + * @param {Mercury} [params.mercury] + * @param {Seed} [params.seed] + * @returns {Agent} + */ + static initialize(params: { + mediatorDID: Domain.DID | string; + pluto: Domain.Pluto; + api?: Domain.Api; + apollo?: Domain.Apollo; + castor?: Domain.Castor; + mercury?: Domain.Mercury; + seed?: Domain.Seed; + options?: AgentOptions; + }): DIDCommAgent { + const mediatorDID = Domain.DID.from(params.mediatorDID); + const pluto = params.pluto; + + const api = params.api ?? new FetchApi(); + const apollo = params.apollo ?? new Apollo(); + 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 pollux = new Pollux(apollo, castor); + const seed = params.seed ?? apollo.createRandomSeed().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 + ); + + return agent; + } + + + /** + * Asyncronously start the agent + * + * @async + * @returns {Promise} + */ + async start(): Promise { + if (this.state !== AgentState.STOPPED) { + return this.state; + } + + try { + this.state = AgentState.STARTING; + await this.pluto.start(); + await this.pollux.start(); + await this.connectionManager.startMediator(); + } catch (e) { + if (e instanceof Domain.AgentError.NoMediatorAvailableError) { + const hostDID = await this.createNewPeerDID([], false); + + await this.connectionManager.registerMediator(hostDID); + + } else throw e; + } + + if (this.connectionManager.mediationHandler.mediator !== undefined) { + await this.connectionManager.startFetchingMessages(5); + this.state = AgentState.RUNNING; + } else { + throw new Domain.AgentError.MediationRequestFailedError("Mediation failed"); + } + + const storedLinkSecret = await this.pluto.getLinkSecret(); + if (storedLinkSecret == null) { + const secret = this.pollux.anoncreds.createLinksecret(); + const linkSecret = new Domain.LinkSecret(secret); + await this.pluto.storeLinkSecret(linkSecret); + } + + return this.state; + } + + /** + * Asyncronously stop the agent and any side task that is running + * + * @async + * @returns {Promise} + */ + async stop(): Promise { + if (this.state !== AgentState.RUNNING) { + return; + } + this.state = AgentState.STOPPING; + await this.connectionManager.stopAllEvents(); + await this.connectionManager.stopFetchingMessages(); + // await this.agent.stop(); + this.state = AgentState.STOPPED; + } + + /** + * Add an event listener to get notified from an Event "MESSAGE" + * + * @param {ListenerKey} eventName + * @param {EventCallback} callback + */ + addListener(eventName: ListenerKey, callback: EventCallback): void { + return this.connectionManager.events.addListener(eventName, callback); + } + + /** + * Remove event listener, used by stop procedure + * @date 20/06/2023 - 14:31:30 + * + * @param {ListenerKey} eventName + * @param {EventCallback} callback + */ + removeListener(eventName: ListenerKey, callback: EventCallback): void { + return this.connectionManager.events.removeListener(eventName, callback); + } + + // get mediationHandler() { + // return this.connectionManager.mediationHandler; + // } + + /** + * Get current mediator DID if available or null + * + * @public + * @readonly + * @type {DID} + */ + get currentMediatorDID() { + return this.mediationHandler.mediator?.mediatorDID; + } + + private runTask(task: Task) { + const ctx = new DIDCommContext({ + MediationHandler: this.mediationHandler, + Mercury: this.mercury, + Api: this.api, + Apollo: this.apollo, + Castor: this.castor, + Pluto: this.pluto, + Pollux: this.pollux, + Seed: this.seed, + }); + + return ctx.run(task); + } + + + /** + * Asyncronously create a new PrismDID + * + * @async + * @param {string} alias + * @param {DIDDocumentService[]} [services=[]] + * @param {?number} [keyPathIndex] + * @returns {Promise} + */ + async createNewPrismDID( + alias: string, + services: Domain.Service[] = [], + keyPathIndex?: number + ): Promise { + const task = new DIDfns.CreatePrismDID({ alias, services, keyPathIndex }); + return this.runTask(task); + } + + /** + * Asyncronously Create a new PeerDID + * + * @async + * @param {DIDDocumentService[]} [services=[]] + * @param {boolean} [updateMediator=true] + * @returns {Promise} + */ + async createNewPeerDID( + services: Domain.Service[] = [], + updateMediator = true + ): Promise { + const task = new CreatePeerDID({ services, updateMediator }); + return this.runTask(task); + } + + /** + * Asyncronously sign a message with a DID + * + * @async + * @param {DID} did + * @param {Uint8Array} message + * @returns {Promise} + */ + async signWith(did: Domain.DID, message: Uint8Array): Promise { + const task = new DIDfns.SignWithDID({ did, message }); + return this.runTask(task); + } + + /** + * Asyncronously parse an invitation from a valid json string + * + * @async + * @param {string} str + * @returns {Promise} + */ + async parseInvitation(str: string): Promise { + const json = JSON.parse(str); + const typeString = findProtocolTypeByValue(json.type); + + switch (typeString) { + case ProtocolType.PrismOnboarding: + return this.parsePrismInvitation(str); + case ProtocolType.Didcomminvitation: + return this.parseOOBInvitation(new URL(str)); + } + + throw new Domain.AgentError.UnknownInvitationTypeError(); + } + + /** + * Handle an invitation to create a connection + * + * @async + * @param {InvitationType} invitation - an OOB or PrismOnboarding invitation + * @returns {Promise} + */ + async acceptInvitation(invitation: InvitationType, optionalAlias?: string): Promise { + if (invitation.type === ProtocolType.Didcomminvitation) { + return this.acceptDIDCommInvitation(invitation, optionalAlias); + } + + if (invitation instanceof PrismOnboardingInvitation) { + return this.acceptPrismOnboardingInvitation(invitation); + } + + throw new Domain.AgentError.InvitationIsInvalidError(); + } + + /** + * Asyncronously parse a prismOnboarding invitation from a string + * + * @async + * @param {string} str + * @returns {Promise} + */ + async parsePrismInvitation(str: string): Promise { + try { + const prismOnboarding = OutOfBandInvitation.parsePrismOnboardingInvitationFromJson(str); + const service = new Domain.Service( + "#didcomm-1", + ["DIDCommMessaging"], + new Domain.ServiceEndpoint(prismOnboarding.onboardEndpoint, ["DIDCommMessaging"]) + ); + const did = await this.createNewPeerDID([service], true); + prismOnboarding.from = did; + + return prismOnboarding; + } catch (e) { + if (e instanceof Error) { + throw new Domain.AgentError.UnknownInvitationTypeError(e.message); + } else { + throw e; + } + } + } + + /** + * Asyncronously accept an onboarding invitation, used to onboard the current DID in the Cloud Agent. + * + * @async + * @param {PrismOnboardingInvitation} invitation + * @returns {Promise} + */ + private async acceptPrismOnboardingInvitation(invitation: PrismOnboardingInvitation): Promise { + if (!invitation.from) { + throw new Domain.AgentError.UnknownInvitationTypeError(); + } + + const response = await this.api.request( + "POST", + invitation.onboardEndpoint, + new Map(), + new Map(), + { + did: invitation.from.toString(), + } + ); + + if (response.httpStatus != 200) { + throw new Domain.AgentError.FailedToOnboardError(); + } + } + + /** + * Asyncronously parse an out of band invitation from a URI as the oob come in format of valid URL + * + * @async + * @param {URL} str + * @returns {Promise} + */ + async parseOOBInvitation(str: URL): Promise { + return new DIDCommInvitationRunner(str).run(); + } + + /** + * Asyncronously accept a didcomm v2 invitation, will create a pair between the Agent + * its connecting with and the current owner's did + * + * @deprecated - use `acceptInvitation` + * @async + * @param {OutOfBandInvitation} invitation + * @returns {*} + */ + async acceptDIDCommInvitation( + invitation: OutOfBandInvitation, + optionalAlias?: string + ): Promise { + if (!this.connectionManager.mediationHandler.mediator) { + throw new Domain.AgentError.NoMediatorAvailableError(); + } + const [attachment] = invitation.attachments ?? []; + const ownDID = await this.createNewPeerDID([], true); + + if (isNil(attachment)) { + const pair = await new DIDCommConnectionRunner( + invitation, + this.pluto, + ownDID, + this.connectionManager, + optionalAlias + ).run(); + + await this.connectionManager.addConnection(pair); + } + else { + const msg = Domain.Message.fromJson({ + ...attachment.payload, + to: ownDID.toString() + }); + await this.pluto.storeMessage(msg); + } + } + + /** + * Start fetching for new messages in such way that it can be stopped at any point in time without causing memory leaks + * + * @param {number} iterationPeriod + */ + async startFetchingMessages(iterationPeriod: number): Promise { + return this.connectionManager.startFetchingMessages(iterationPeriod); + } + + /** + * Stops fetching messages + */ + stopFetchingMessages(): void { + this.connectionManager.stopFetchingMessages(); + } + + /** + * Asyncronously send a didcomm Message + * + * @param {Message} message + * @returns {Promise} + */ + sendMessage(message: Domain.Message): Promise { + return this.connectionManager.sendMessage(message); + } + + /** + * + * @param credential + * @returns + */ + isCredentialRevoked(credential: Domain.Credential) { + return this.pollux.isCredentialRevoked(credential); + } + + /** + * This method can be used by holders in order to disclose the value of a Credential + * JWT are just encoded plainText + * Anoncreds will really need to be disclosed as the fields are encoded. + * + * @param {Credential} credential + * @returns {AttributeType} + */ + async revealCredentialFields(credential: Domain.Credential, fields: string[], linkSecret: string) { + return this.pollux.revealCredentialFields(credential, fields, linkSecret); + } + + /** + * Asyncronously get all verifiable credentials + * + * @returns {Promise} + */ + verifiableCredentials(): Promise { + return this.pluto.getAllCredentials(); + } + + /** + * Asyncronously prepare a request credential message from a valid offerCredential for now supporting w3c verifiable credentials offers. + * + * @async + * @param {OfferCredential} offer + * @returns {Promise} + */ + async prepareRequestCredentialWithIssuer( + offer: OfferCredential + ): Promise { + const task = new HandleOfferCredential({ offer }); + return this.runTask(task); + } + + /** + * Extract the verifiableCredential object from the Issue credential message asyncronously + * + * @async + * @param {IssueCredential} message + * @returns {Promise} + */ + async processIssuedCredentialMessage( + issueCredential: IssueCredential + ): Promise { + const task = new HandleIssueCredential({ issueCredential }); + return this.runTask(task); + } + + /** + * Asyncronously create a verifiablePresentation from a valid stored verifiableCredential + * This is used when the verified requests a specific verifiable credential, this will create the actual + * instance of the presentation which we can share with the verifier. + * + * @async + * @param {RequestPresentation} request + * @param {VerifiableCredential} credential + * @returns {Promise} + */ + async createPresentationForRequestProof( + request: RequestPresentation, + credential: Domain.Credential + ): Promise { + const task = new CreatePresentation({ request, credential }); + return this.runTask(task); + } + + /** + * Initiate a PresentationRequest from the SDK, to create oob Verification Requests + * @param {Domain.CredentialType} type + * @param {Domain.DID} toDID + * @param {ProofTypes[]} proofTypes[] + * @returns + * + * 1. Example use-case: Send a Presentation Request for a JWT credential issued by a specific issuer + * ```ts + * agent.initiatePresentationRequest( + * Domain.CredentialType.JWT, + * toDID, + * { issuer: Domain.DID.fromString("did:peer:12345"), claims: {}} + * ); + * ``` + * + * 2. Example use-case: Send a Presentation Request for a JWT credential issued by a specific issuer and specific claims + * ```ts + * agent.initiatePresentationRequest( + * Domain.CredentialType.JWT, + * toDID, + * { issuer: Domain.DID.fromString("did:peer:12345"), claims: {email: {type: 'string', pattern:'email@email.com'}}} + * ); + * ``` + */ + async initiatePresentationRequest(type: T, toDID: Domain.DID, presentationClaims: Domain.PresentationClaims): Promise { + const task = new CreatePresentationRequest({ type, toDID, claims: presentationClaims }); + const requestPresentation = await this.runTask(task); + const requestPresentationMessage = requestPresentation.makeMessage(); + await this.connectionManager.sendMessage(requestPresentationMessage); + + return requestPresentation; + } + + /** + * Initiate the Presentation and presentationSubmission + * @param presentation + */ + async handlePresentation(presentation: Presentation): Promise { + const task = new HandlePresentation({ presentation }); + return this.runTask(task); + } +} diff --git a/src/edge-agent/didcomm/Context.ts b/src/edge-agent/didcomm/Context.ts new file mode 100644 index 000000000..2505ea465 --- /dev/null +++ b/src/edge-agent/didcomm/Context.ts @@ -0,0 +1,12 @@ +import { Task } from "../../utils/tasks"; +import { MediatorHandler } from "../types"; + +interface Deps { + MediationHandler: MediatorHandler; +} + +export class DIDCommContext extends Task.Context { + get MediationHandler() { + return this.getProp("MediationHandler"); + } +} diff --git a/src/edge-agent/didcomm/CreatePeerDID.ts b/src/edge-agent/didcomm/CreatePeerDID.ts new file mode 100644 index 000000000..7b96074c6 --- /dev/null +++ b/src/edge-agent/didcomm/CreatePeerDID.ts @@ -0,0 +1,66 @@ +import * as Domain from "../../domain"; +import { Task } from "../../utils/tasks"; +import { DIDCommContext } from "./Context"; + +/** + * Asyncronously create and store a new peer did + * + * @async + * @param {Service[]} services + * @param {boolean} [updateMediator=false] + * @returns {Promise} + */ + +interface Args { + services: Domain.Service[]; + updateMediator: boolean; +} + +export class CreatePeerDID extends Task { + async run(ctx: DIDCommContext): Promise { + const services = this.args.services; + const updateMediator = this.args.updateMediator ?? false; + const publicKeys: Domain.PublicKey[] = []; + const keyAgreementPrivateKey = ctx.Apollo.createPrivateKey({ + type: Domain.KeyTypes.Curve25519, + curve: Domain.Curve.X25519, + }); + + const authenticationPrivateKey = ctx.Apollo.createPrivateKey({ + type: Domain.KeyTypes.EC, + curve: Domain.Curve.ED25519, + }); + + publicKeys.push(keyAgreementPrivateKey.publicKey()); + publicKeys.push(authenticationPrivateKey.publicKey()); + const mediatorDID = ctx.MediationHandler.mediator?.mediatorDID; + + if ( + mediatorDID && + !services.find((service) => { + return service.isDIDCommMessaging; + }) + ) { + //TODO This still needs to be done update the key List + services.push( + new Domain.Service( + "#didcomm-1", + ["DIDCommMessaging"], + new Domain.ServiceEndpoint(mediatorDID.toString()) + ) + ); + } + const did = await ctx.Castor.createPeerDID(publicKeys, services); + + if (updateMediator) { + await ctx.MediationHandler.updateKeyListWithDIDs([did]); + } + + await ctx.Pluto.storeDID(did, [ + keyAgreementPrivateKey, + authenticationPrivateKey, + ]); + + return did; + } +} diff --git a/src/edge-agent/didcomm/CreatePresentation.ts b/src/edge-agent/didcomm/CreatePresentation.ts new file mode 100644 index 000000000..794275b71 --- /dev/null +++ b/src/edge-agent/didcomm/CreatePresentation.ts @@ -0,0 +1,178 @@ +import { uuid } from "@stablelib/uuid"; +import * as Domain from "../../domain"; +import { AnonCredsCredential } from "../../pollux/models/AnonCredsVerifiableCredential"; +import { JWTCredential } from "../../pollux/models/JWTVerifiableCredential"; +import { PresentationRequest } from "../../pollux/models/PresentationRequest"; +import { SDJWTCredential } from "../../pollux/models/SDJWTVerifiableCredential"; +import { Presentation, RequestPresentation } from "../protocols/proofPresentation"; +import { DIDCommContext } from "./Context"; +import { Task } from "../../utils/tasks"; + +/** + * Asyncronously create a verifiablePresentation from a valid stored verifiableCredential + * This is used when the verified requests a specific verifiable credential, this will create the actual + * instance of the presentation which we can share with the verifier. + */ + +interface Args { + credential: Domain.Credential; + request: RequestPresentation; +} + +export class CreatePresentation extends Task { + async run(ctx: DIDCommContext) { + const { credential, request } = this.args; + + const attachment = request.attachments.at(0); + if (!attachment) { + throw new Domain.AgentError.OfferDoesntProvideEnoughInformation(); + } + const attachmentFormat = attachment.format ?? 'unknown'; + const presentationRequest = this.parseProofRequest(attachment); + const proof = attachmentFormat === Domain.AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS ? + await this.handlePresentationDefinitionRequest(ctx, presentationRequest, credential) : + await this.handlePresentationRequest(ctx, presentationRequest, credential); + + const presentationAttachment = Domain.AttachmentDescriptor.build( + proof, + uuid(), + 'application/json', + undefined, + Domain.AttachmentFormats.ANONCREDS_PROOF + ); + const presentation = new Presentation( + { + comment: request.body.comment, + goalCode: request.body.goalCode + }, + [ + presentationAttachment, + ], + request.to, + request.from, + request.thid ?? request.id + ); + + return presentation; + } + + /** + * match the Proof request to return relevant PresentationRequest. + * Proof Request comes from a Message Attachment. + * + * @param {AttachmentDescriptor} data - presentation proof request + * @returns {PresentationRequest} + * @throws + */ + private parseProofRequest(attachment: Domain.AttachmentDescriptor) { + const data = Domain.Message.Attachment.extractJSON(attachment); + if (attachment.format === Domain.AttachmentFormats.ANONCREDS_PROOF_REQUEST) { + return new PresentationRequest(Domain.AttachmentFormats.AnonCreds, data); + } + if (attachment.format === Domain.CredentialType.JWT) { + return new PresentationRequest(Domain.AttachmentFormats.JWT, data); + } + if (attachment.format === Domain.CredentialType.SDJWT) { + return new PresentationRequest(Domain.AttachmentFormats.SDJWT, data); + } + if (attachment.format === Domain.AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS) { + return new PresentationRequest(Domain.AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS, data); + } + + throw new Error("Unsupported Proof Request"); + } + + + private async handlePresentationDefinitionRequest( + ctx: DIDCommContext, + request: PresentationRequest, + credential: Domain.Credential, + ): Promise { + if (request.isType(Domain.AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS)) { + if (credential instanceof JWTCredential) { + const privateKeys = await ctx.Pluto.getDIDPrivateKeysByDID(Domain.DID.fromString(credential.subject)); + const privateKey = privateKeys.at(0); + if (!privateKey) { + throw new Error("Undefined privatekey from credential subject."); + } + if (!request.isType(Domain.AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS)) { + throw new Error("Undefined privatekey from credential subject."); + } + const presentationSubmission = await ctx.Pollux.createPresentationSubmission( + request.toJSON(), + credential, + privateKey + ); + return JSON.stringify(presentationSubmission); + } + } + if (request.isType(Domain.AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS)) { + if (credential instanceof AnonCredsCredential) { + const storedLinkSecret = await ctx.Pluto.getLinkSecret(); + if (!storedLinkSecret) { + throw new Error("Link secret not found."); + } + + const req = request.toJSON(); + const presentationSubmission = await ctx.Pollux.createPresentationSubmission( + req as any, + credential, + storedLinkSecret + ); + return JSON.stringify(presentationSubmission); + } + } + + throw new Error("Not implemented"); + } + + private async handlePresentationRequest( + ctx: DIDCommContext, + request: PresentationRequest, + credential: Domain.Credential + ): Promise { + if (credential instanceof SDJWTCredential && request.isType(Domain.AttachmentFormats.SDJWT)) { + if (!credential.isProvable()) { + throw new Error("Credential is not Provable"); + } + const subjectDID = Domain.DID.from(credential.subject); + const prismPrivateKeys = await ctx.Pluto.getDIDPrivateKeysByDID(subjectDID); + const prismPrivateKey = prismPrivateKeys.find((key) => key.curve === Domain.Curve.ED25519); + if (prismPrivateKey === undefined) { + throw new Domain.AgentError.CannotFindDIDPrivateKey(); + } + const signedJWT = await ctx.Pollux.createPresentationProof(request, credential, { + did: subjectDID, + privateKey: prismPrivateKey + }); + return signedJWT; + } + + if (credential instanceof AnonCredsCredential && request.isType(Domain.AttachmentFormats.AnonCreds)) { + const linkSecret = await ctx.Pluto.getLinkSecret(); + if (!linkSecret) { + throw new Domain.AgentError.CannotFindLinkSecret(); + } + const presentation = await ctx.Pollux.createPresentationProof(request, credential, { linkSecret }); + return presentation; + } + if (credential instanceof JWTCredential && request.isType(Domain.AttachmentFormats.JWT)) { + if (!credential.isProvable()) { + throw new Error("Credential is not Provable"); + } + const subjectDID = Domain.DID.from(credential.subject); + const prismPrivateKeys = await ctx.Pluto.getDIDPrivateKeysByDID(subjectDID); + const prismPrivateKey = prismPrivateKeys.find((key) => key.curve === Domain.Curve.SECP256K1); + if (prismPrivateKey === undefined) { + throw new Domain.AgentError.CannotFindDIDPrivateKey(); + } + const signedJWT = await ctx.Pollux.createPresentationProof(request, credential, { + did: subjectDID, + privateKey: prismPrivateKey + }); + return signedJWT; + } + + throw new Domain.AgentError.UnhandledPresentationRequest(); + } +} diff --git a/src/edge-agent/didcomm/CreatePresentationRequest.ts b/src/edge-agent/didcomm/CreatePresentationRequest.ts new file mode 100644 index 000000000..f79f78d9d --- /dev/null +++ b/src/edge-agent/didcomm/CreatePresentationRequest.ts @@ -0,0 +1,97 @@ +import { uuid } from "@stablelib/uuid"; +import * as Domain from "../../domain"; +import { validatePresentationClaims } from "../../pollux/utils/claims"; +import { RequestPresentation } from "../protocols/proofPresentation"; +import { CreatePeerDID } from "./CreatePeerDID"; +import { Task } from "../../utils/tasks"; +import { DIDCommContext } from "./Context"; + +interface Args { + type: Domain.CredentialType; + toDID: Domain.DID; + claims: Domain.PresentationClaims; +} + +export class CreatePresentationRequest extends Task { + async run(ctx: DIDCommContext) { + const { claims, toDID, type } = this.args; + const didDocument = await ctx.Castor.resolveDID(toDID.toString()); + const peerDIDTask = new CreatePeerDID({ services: didDocument.services, updateMediator: true }); + const newPeerDID = await ctx.run(peerDIDTask); + + if (type === Domain.CredentialType.AnonCreds) { + if (!validatePresentationClaims(claims, Domain.CredentialType.AnonCreds)) { + throw new Domain.PolluxError.InvalidPresentationDefinitionError("Anoncreds Claims are invalid"); + } + + const presentationDefinitionRequest = await ctx.Pollux.createPresentationDefinitionRequest( + type, + claims, + new Domain.PresentationOptions({}, Domain.CredentialType.AnonCreds) + ); + + return this.createRequest( + type, + presentationDefinitionRequest, + newPeerDID, + toDID + ); + } + + if (type === Domain.CredentialType.JWT) { + if (!validatePresentationClaims(claims, Domain.CredentialType.JWT)) { + throw new Domain.PolluxError.InvalidPresentationDefinitionError("JWT Claims are invalid"); + } + + const presentationDefinitionRequest = await ctx.Pollux.createPresentationDefinitionRequest( + type, + claims, + new Domain.PresentationOptions({ + jwt: { + jwtAlg: [Domain.curveToAlg(Domain.Curve.SECP256K1)] + }, + challenge: "Sign this text " + uuid(), + domain: 'N/A' + }) + ); + + return this.createRequest( + type, + presentationDefinitionRequest, + newPeerDID, + toDID + ); + } + + throw new Domain.PolluxError.CredentialTypeNotSupported(); + } + + private createRequest( + type: Domain.CredentialType, + definition: Domain.PresentationDefinitionRequest, + from: Domain.DID, + to: Domain.DID + ) { + const attachmentFormat = type === Domain.CredentialType.JWT ? + Domain.AttachmentFormats.PRESENTATION_EXCHANGE_DEFINITIONS : + Domain.AttachmentFormats.ANONCREDS_PROOF_REQUEST; + + return new RequestPresentation( + { + proofTypes: [], + }, + [ + Domain.AttachmentDescriptor.build( + { json: definition }, + uuid(), + 'application/json', + undefined, + attachmentFormat + ) + ], + from, + to, + uuid() + ); + } +} diff --git a/src/edge-agent/didcomm/HandleIssueCredential.ts b/src/edge-agent/didcomm/HandleIssueCredential.ts new file mode 100644 index 000000000..7ca5418ef --- /dev/null +++ b/src/edge-agent/didcomm/HandleIssueCredential.ts @@ -0,0 +1,57 @@ +import * as Domain from "../../domain"; +import { Task } from "../../utils/tasks"; +import { IssueCredential } from "../protocols/issueCredential/IssueCredential"; +import { DIDCommContext } from "./Context"; + +/** + * Extract the verifiableCredential object from the Issue credential message asyncronously + */ + +interface Args { + issueCredential: IssueCredential; +} + +export class HandleIssueCredential extends Task { + async run(ctx: DIDCommContext) { + const { issueCredential } = this.args; + const message = issueCredential.makeMessage(); + const credentialType = message.credentialFormat; + const attachment = message.attachments.at(0); + + if (!attachment) { + throw new Error("No attachment"); + } + + if (!issueCredential.thid) { + throw new Error("No thid"); + } + + const parseOpts: Domain.CredentialIssueOptions = { + type: credentialType, + }; + + const payload = typeof attachment.payload === 'string' ? attachment.payload : JSON.stringify(attachment.payload); + const credData = Uint8Array.from(Buffer.from(payload)); + + if (credentialType === Domain.CredentialType.AnonCreds) { + const linkSecret = await ctx.Pluto.getLinkSecret(); + parseOpts.linkSecret = linkSecret?.secret; + + const credentialMetadata = await ctx.Pluto.getCredentialMetadata( + issueCredential.thid + ); + + if (!credentialMetadata || !credentialMetadata.isType(Domain.CredentialType.AnonCreds)) { + throw new Error("Invalid credential Metadata"); + } + + parseOpts.credentialMetadata = credentialMetadata.toJSON(); + } + + const credential = await ctx.Pollux.parseCredential(credData, parseOpts); + + await ctx.Pluto.storeCredential(credential); + + return credential; + } +} diff --git a/src/edge-agent/didcomm/HandleOfferCredential.ts b/src/edge-agent/didcomm/HandleOfferCredential.ts new file mode 100644 index 000000000..796cca1a3 --- /dev/null +++ b/src/edge-agent/didcomm/HandleOfferCredential.ts @@ -0,0 +1,170 @@ +import { base64 } from "multiformats/bases/base64"; +import * as Domain from "../../domain"; +import { PrismKeyPathIndexTask } from "../didFunctions/PrismKeyPathIndex"; +import { OfferCredential } from "../protocols/issueCredential/OfferCredential"; +import { RequestCredential, createRequestCredentialBody } from "../protocols/issueCredential/RequestCredential"; +import { Task } from "../../utils/tasks"; +import { DIDCommContext } from "./Context"; + +/** + * Asyncronously prepare a request credential message from a valid offerCredential + * for now supporting w3c verifiable credentials offers. + */ + +interface Args { + offer: OfferCredential; +} + +export class HandleOfferCredential extends Task { + async run(ctx: DIDCommContext) { + const { offer } = this.args; + const attachment = offer.attachments.at(0); + + if (!attachment) { + throw new Error("Invalid attachment"); + } + + const credentialType = offer.makeMessage().credentialFormat; + const payload = attachment.payload; + let credRequestBuffer: string; + + const requestCredentialBody = createRequestCredentialBody( + [], + offer.body.goalCode, + offer.body.comment + ); + + const from = offer.to; + const to = offer.from; + if (!from) { + throw new Error("Missing from"); + } + if (!to) { + throw new Error("Missing to"); + } + const thid = offer.thid; + const credentialFormat = + credentialType === Domain.CredentialType.AnonCreds ? Domain.AttachmentFormats.ANONCREDS_REQUEST : + credentialType === Domain.CredentialType.JWT ? Domain.CredentialType.JWT : + credentialType === Domain.CredentialType.SDJWT ? Domain.CredentialType.SDJWT : + Domain.CredentialType.Unknown; + + if (credentialType === Domain.CredentialType.AnonCreds) { + const metaname = offer.thid; + if (!metaname) { + throw new Error("Missing offer.thid"); + } + + const linkSecret = await ctx.Pluto.getLinkSecret(); + if (!linkSecret) { + throw new Error("No linkSecret available."); + } + + const [credentialRequest, credentialRequestMetadata] = + await ctx.Pollux.processCredentialOffer(payload, { linkSecret }); + + credRequestBuffer = JSON.stringify(credentialRequest); + + const metadata = new Domain.CredentialMetadata(Domain.CredentialType.AnonCreds, metaname, credentialRequestMetadata); + + await ctx.Pluto.storeCredentialMetadata(metadata); + } + else if (credentialType === Domain.CredentialType.JWT) { + const getIndexTask = new PrismKeyPathIndexTask({}); + const index = await ctx.run(getIndexTask); + + const privateKey = await ctx.Apollo.createPrivateKey({ + [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, + [Domain.KeyProperties.index]: index, + [Domain.KeyProperties.type]: Domain.KeyTypes.EC, + [Domain.KeyProperties.seed]: Buffer.from(ctx.Seed.value).toString("hex"), + }); + + const did = await ctx.Castor.createPrismDID(privateKey.publicKey()); + + await ctx.Pluto.storeDID(did, privateKey); + + credRequestBuffer = await ctx.Pollux.processCredentialOffer(payload, { + did: did, + keyPair: { + curve: Domain.Curve.SECP256K1, + privateKey: privateKey, + publicKey: privateKey.publicKey(), + }, + }); + } + else if (credentialType === Domain.CredentialType.SDJWT) { + const getIndexTask = new PrismKeyPathIndexTask({}); + const index = await ctx.run(getIndexTask); + + const masterSk = await ctx.Apollo.createPrivateKey({ + [Domain.KeyProperties.curve]: Domain.Curve.SECP256K1, + [Domain.KeyProperties.index]: index, + [Domain.KeyProperties.type]: Domain.KeyTypes.EC, + [Domain.KeyProperties.seed]: Buffer.from(ctx.Seed.value).toString("hex"), + }); + + const getIndexTask2 = new PrismKeyPathIndexTask({}); + const index2 = await ctx.run(getIndexTask2); + + const issSK = await ctx.Apollo.createPrivateKey({ + [Domain.KeyProperties.curve]: Domain.Curve.ED25519, + [Domain.KeyProperties.index]: index2, + [Domain.KeyProperties.type]: Domain.KeyTypes.EC, + [Domain.KeyProperties.seed]: Buffer.from(ctx.Seed.value).toString("hex"), + }); + + const did = await ctx.Castor.createPrismDID( + masterSk.publicKey(), + [], + [ + issSK.publicKey() + ] + ); + + await ctx.Pluto.storeDID(did, [masterSk, issSK]); + + credRequestBuffer = await ctx.Pollux.processCredentialOffer(payload, { + did: did, + sdJWT: true, + keyPair: { + curve: Domain.Curve.SECP256K1, + privateKey: masterSk, + publicKey: masterSk.publicKey(), + }, + }); + } else { + throw new Domain.AgentError.InvalidCredentialFormats(); + } + + + const attachments = [ + new Domain.AttachmentDescriptor( + { + base64: base64.baseEncode(Buffer.from(credRequestBuffer)), + }, + credentialFormat, + undefined, + undefined, + credentialFormat + ), + ]; + + const requestCredential = new RequestCredential( + requestCredentialBody, + attachments, + from, + to, + thid + ); + + attachments.forEach((attachment) => { + requestCredential.body.formats.push({ + attach_id: attachment.id, + format: `${credentialFormat}`, + }); + }); + + return requestCredential; + } +} diff --git a/src/edge-agent/didcomm/HandlePresentation.ts b/src/edge-agent/didcomm/HandlePresentation.ts new file mode 100644 index 000000000..e6281ceaf --- /dev/null +++ b/src/edge-agent/didcomm/HandlePresentation.ts @@ -0,0 +1,51 @@ +import * as Domain from "../../domain"; +import { asJsonObj } from "../../utils"; +import { Task } from "../../utils/tasks"; +import { Presentation } from "../protocols/proofPresentation"; +import { ProtocolType } from "../protocols/ProtocolTypes"; +import { DIDCommContext } from "./Context"; + +interface Args { + presentation: Presentation; +} + +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"); + } + if (!presentation.thid) { + throw new Domain.AgentError.UnsupportedAttachmentType("Cannot find any message with that threadID"); + } + + // TODO fix types with validation + const presentationSubmission = asJsonObj(attachment.payload) as any; + const presentationDefinitionRequest = await this.getPresentationDefinitionByThid(ctx, presentation.thid); + const verified = await ctx.Pollux.verifyPresentationSubmission( + presentationSubmission, + { presentationDefinitionRequest } + ); + + return verified; + } + + private async getPresentationDefinitionByThid(ctx: DIDCommContext, thid: string): Promise { + const allMessages = await ctx.Pluto.getAllMessages(); + const message = allMessages.find((message) => { + return message.thid === thid && message.piuri === ProtocolType.DidcommRequestPresentation; + }); + + if (message) { + const attachment = message.attachments.at(0); + if (!attachment) { + throw new Domain.AgentError.UnsupportedAttachmentType("Invalid presentation message, attachment missing"); + } + const presentationDefinitionRequest = Domain.Message.Attachment.extractJSON(attachment); + return presentationDefinitionRequest; + } + + throw new Domain.AgentError.UnsupportedAttachmentType("Cannot find any message with that threadID"); + } +} diff --git a/src/edge-agent/types/index.ts b/src/edge-agent/types/index.ts index 2cb465638..382188ac0 100644 --- a/src/edge-agent/types/index.ts +++ b/src/edge-agent/types/index.ts @@ -3,24 +3,13 @@ import { DID, Mediator, Message, - Service as DIDDocumentService, - Signature, Credential, - CredentialType, - PresentationClaims, - KeyPair, - PublicKey, } from "../../domain"; import { DIDPair } from "../../domain/models/DIDPair"; import { Castor } from "../../domain/buildingBlocks/Castor"; import { Mercury } from "../../domain/buildingBlocks/Mercury"; import { Pluto } from "../../domain/buildingBlocks/Pluto"; import { CancellableTask } from "../helpers/Task"; -import { OfferCredential } from "../protocols/issueCredential/OfferCredential"; -import { RequestCredential } from "../protocols/issueCredential/RequestCredential"; -import { IssueCredential } from "../protocols/issueCredential/IssueCredential"; -import { RequestPresentation } from "../protocols/proofPresentation/RequestPresentation"; -import { Presentation } from "../protocols/proofPresentation/Presentation"; interface InvitationInterface { type: InvitationTypes; @@ -35,9 +24,9 @@ export enum InvitationTypes { export type AgentOptions = { experiments?: { - liveMode?: boolean - } -} + liveMode?: boolean; + }; +}; export type InvitationType = PrismOnboardingInvitation | OutOfBandInvitation; @@ -56,62 +45,6 @@ export class PrismOnboardingInvitation implements InvitationInterface { } -export interface AgentCredentials { - revealCredentialFields: (credential: Credential, fields: string[], linkSecret: string) => Promise<{ - [name: string]: any - }>; - isCredentialRevoked: (credential: Credential) => Promise; - - - - prepareRequestCredentialWithIssuer( - offer: OfferCredential - ): Promise; - processIssuedCredentialMessage(message: IssueCredential): Promise; - - verifiableCredentials(): Promise; - - initiatePresentationRequest(type: CredentialType.JWT, toDID: DID, claims: PresentationClaims): Promise; - - initiatePresentationRequest(type: CredentialType.AnonCreds, toDID: DID, claims: PresentationClaims): Promise; - - - createPresentationForRequestProof( - request: RequestPresentation, - credential: Credential - ): Promise; - - handlePresentation(presentation: Presentation): Promise -} - -export interface AgentDIDHigherFunctions { - signWith(did: DID, message: Uint8Array): Promise; - - createNewPeerDID( - services: DIDDocumentService[], - updateMediator: boolean - ): Promise; - - createNewPrismDID( - alias: string, - services: DIDDocumentService[], - keyPathIndex?: number, - issuingKeys?: (PublicKey | KeyPair)[] - ): Promise; -} - -export interface AgentInvitations { - acceptDIDCommInvitation(invitation: OutOfBandInvitation, optionalAlias?: string): Promise; - - parseInvitation(str: string): Promise; - - acceptInvitation(invitation: PrismOnboardingInvitation, optionalAlias?: string): Promise; - - parsePrismInvitation(str: string): Promise; - - parseOOBInvitation(str: URL): Promise; -} - type MessageEventArg = Message[]; type ConnectionEventArg = DIDPair; type RevokeEventArg = Credential; @@ -142,7 +75,6 @@ export interface ConnectionsManager { castor: Castor; mercury: Mercury; pluto: Pluto; - agentCredentials: AgentCredentials; mediationHandler: MediatorHandler; pairings: DIDPair[]; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -180,7 +112,7 @@ export abstract class MediatorHandler { abstract pickupUnreadMessages( limit: number - ): Promise>; + ): Promise>; abstract registerMessagesAsRead(ids: string[]): Promise; @@ -191,5 +123,5 @@ export abstract class MediatorHandler { attachmentId: string; message: Message; }[]) => void | Promise - ): void + ): void; } diff --git a/src/index.ts b/src/index.ts index 065dc9b3b..0b3d46c67 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,7 +6,10 @@ export * as Domain from "./domain"; export { default as Mercury } from "./mercury/Mercury"; export * from "./pluto"; export { default as Pollux } from "./pollux/Pollux"; -export { default as Agent } from "./edge-agent/Agent"; +// alias DIDCommAgent as Agent to hide breaking changes +export { default as Agent } from "./edge-agent/didcomm/Agent"; +// export { default as Agent } from "./edge-agent/Agent"; +// export { default as DIDCommAgent } from "./edge-agent/didcomm/Agent"; export * from "./edge-agent/protocols/other/BasicMessage"; export { IssueCredential } from "./edge-agent/protocols/issueCredential/IssueCredential"; export { OfferCredential } from "./edge-agent/protocols/issueCredential/OfferCredential"; @@ -24,9 +27,6 @@ export type { MediatorHandler, ConnectionsManager as ConnectionsManagerInterface, MediatorStore, - AgentCredentials, - AgentInvitations, - AgentDIDHigherFunctions, AgentMessageEvents, } from "./edge-agent/types"; export type { DIDCommProtocol } from "./mercury/DIDCommProtocol"; diff --git a/src/pollux/Pollux.ts b/src/pollux/Pollux.ts index 9c49384f7..46caddcc8 100644 --- a/src/pollux/Pollux.ts +++ b/src/pollux/Pollux.ts @@ -1171,6 +1171,7 @@ export default class Pollux implements IPollux { * @param did * @param privateKey * @returns {string} kid (key identifier) + * // ??? replaced by CreateJWT task */ private async getSigningKid(did: DID, privateKey: PrivateKey) { const pubKey = privateKey.publicKey(); diff --git a/src/pollux/models/JWTVerifiableCredential.ts b/src/pollux/models/JWTVerifiableCredential.ts index b1ace4491..d92040b27 100644 --- a/src/pollux/models/JWTVerifiableCredential.ts +++ b/src/pollux/models/JWTVerifiableCredential.ts @@ -1,4 +1,4 @@ -import { Pluto } from "../../domain"; +import { JWT, Pluto } from "../../domain"; import { Credential, ProvableCredential, @@ -18,7 +18,6 @@ import { W3CVerifiableCredentialType, W3CVerifiablePresentation, } from "../../domain/models/VerifiableCredential"; -import { decodeJWS } from "../utils/decodeJWS"; export const JWTVerifiableCredentialRecoveryId = "jwt+credential"; @@ -31,8 +30,8 @@ export class JWTCredential public recoveryId = JWTVerifiableCredentialRecoveryId; public properties = new Map(); - constructor(payload: string, revoked?: boolean) - constructor(payload: JWTCredentialPayload | JWTPresentationPayload, revoked?: boolean) + constructor(payload: string, revoked?: boolean); + constructor(payload: JWTCredentialPayload | JWTPresentationPayload, revoked?: boolean); constructor( payload: any, revoked = false @@ -41,9 +40,9 @@ export class JWTCredential let originalString: string | undefined; if (typeof payload === 'string') { - const jwtObject = decodeJWS(payload) + const jwtObject = JWT.decode(payload); originalString = payload; - payload = jwtObject.payload + payload = jwtObject.payload; } else { originalString = payload.jti; } @@ -85,35 +84,35 @@ export class JWTCredential this.properties.set( JWT_VC_PROPS.exp, payload[JWT_VC_PROPS.exp] - ) + ); } if (originalString) { this.properties.set( JWT_VC_PROPS.jti, originalString - ) + ); } if (payload[JWT_VC_PROPS.iss]) { this.properties.set( JWT_VC_PROPS.iss, payload[JWT_VC_PROPS.iss] - ) + ); } if (payload[JWT_VC_PROPS.sub]) { this.properties.set( JWT_VC_PROPS.sub, payload[JWT_VC_PROPS.sub] - ) + ); } if (payload[JWT_VC_PROPS.nbf]) { this.properties.set( JWT_VC_PROPS.nbf, payload[JWT_VC_PROPS.nbf] - ) + ); } } else { //Set properties for a JWTCredential Presentation @@ -121,62 +120,62 @@ export class JWTCredential this.properties.set( JWT_VP_PROPS.iss, payload[JWT_VP_PROPS.iss] - ) + ); } if (originalString) { this.properties.set( JWT_VC_PROPS.jti, originalString - ) + ); } if (payload[JWT_VP_PROPS.aud]) { this.properties.set( JWT_VP_PROPS.aud, payload[JWT_VP_PROPS.aud] - ) + ); } if (payload[JWT_VP_PROPS.nbf]) { this.properties.set( JWT_VP_PROPS.nbf, payload[JWT_VP_PROPS.nbf] - ) + ); } if (payload[JWT_VP_PROPS.exp]) { this.properties.set( JWT_VP_PROPS.exp, payload[JWT_VP_PROPS.exp] - ) + ); } if (payload[JWT_VP_PROPS.nonce]) { this.properties.set( JWT_VP_PROPS.nonce, payload[JWT_VP_PROPS.nonce] - ) + ); } if (payload[JWT_VP_PROPS.nbf]) { this.properties.set( JWT_VP_PROPS.nbf, payload[JWT_VP_PROPS.nbf] - ) + ); } if (payload[JWT_VP_PROPS.vp]) { this.properties.set( JWT_VP_PROPS.vp, payload[JWT_VP_PROPS.vp] - ) + ); } } } static fromJWS(jws: string, revoked?: boolean): JWTCredential { - return new JWTCredential(jws, revoked) + return new JWTCredential(jws, revoked); } private isCredentialPayload(payload: any): payload is JWTCredentialPayload { @@ -262,7 +261,7 @@ export class JWTCredential } get isCredential() { - return this.isCredentialPayload(Object.fromEntries(this.properties)) + return this.isCredentialPayload(Object.fromEntries(this.properties)); } get id() { @@ -295,7 +294,7 @@ export class JWTCredential this.credentialSubject ]; } - return [] + return []; } get context() { @@ -336,7 +335,7 @@ export class JWTCredential const aud = this.isCredentialPayload(Object.fromEntries(this.properties)) ? this.properties.get(JWT_VC_PROPS.aud) : this.properties.get(JWT_VP_PROPS.aud); - return aud + return aud; } get issuer() { @@ -354,7 +353,7 @@ export class JWTCredential if (this.isCredentialPayload(Object.fromEntries(this.properties))) { return this.properties.get(JWT_VC_PROPS.sub); } else { - throw new InvalidCredentialError("Subject is only available in a VC") + throw new InvalidCredentialError("Subject is only available in a VC"); } } @@ -376,7 +375,7 @@ export class JWTCredential presentation(): W3CVerifiablePresentation { if (!this.isCredentialPayload(Object.fromEntries(this.properties))) { - throw new InvalidCredentialError("Invalid payload is not VC") + throw new InvalidCredentialError("Invalid payload is not VC"); } return { "@context": [ @@ -393,7 +392,7 @@ export class JWTCredential verifiableCredential(): W3CVerifiableCredential { if (!this.isCredentialPayload(Object.fromEntries(this.properties))) { - throw new InvalidCredentialError("Invalid payload is not VC") + throw new InvalidCredentialError("Invalid payload is not VC"); } return { "@context": [ @@ -412,7 +411,7 @@ export class JWTCredential toStorable() { const id = this.id; const data = { id, ...Object.fromEntries(this.properties) }; - const claims = this.claims.map((claim) => typeof claim !== 'string' ? JSON.stringify(claim) : claim) + const claims = this.claims.map((claim) => typeof claim !== 'string' ? JSON.stringify(claim) : claim); return { id, recoveryId: this.recoveryId, diff --git a/src/pollux/utils/JWT.ts b/src/pollux/utils/JWT.ts index 71e04beee..f4bb737ee 100644 --- a/src/pollux/utils/JWT.ts +++ b/src/pollux/utils/JWT.ts @@ -31,10 +31,12 @@ export class JWT extends JWTCore { if (!verificationMethods) { throw new Error("Invalid did document"); } + const jwtObject = JWTCredential.fromJWS(jws); if (jwtObject.issuer !== issuerDID.toString()) { throw new Error("Invalid issuer"); } + if (jwtObject.isCredential && holderDID && holderDID.toString() !== jwtObject.subject) { throw new Error("Invalid subject (holder)"); } diff --git a/src/pollux/utils/SDJWT.ts b/src/pollux/utils/SDJWT.ts index efd5bc098..24496ec59 100644 --- a/src/pollux/utils/SDJWT.ts +++ b/src/pollux/utils/SDJWT.ts @@ -30,8 +30,8 @@ export class SDJWT extends JWTCore { throw new Error("Invalid issuer"); } for (const verificationMethod of verificationMethods) { - const pk: Domain.PublicKey | undefined = this.getPKInstance(verificationMethod) - if (pk && pk.canVerify()) { + const pk = this.getPKInstance(verificationMethod) + if (pk?.canVerify()) { const sdjwt = new SDJwtVcInstance(this.getPKConfig(pk)); try { await sdjwt.verify( diff --git a/src/pollux/utils/decodeJWS.ts b/src/pollux/utils/decodeJWS.ts deleted file mode 100644 index 6ca96ffbc..000000000 --- a/src/pollux/utils/decodeJWS.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { base64url } from "multiformats/bases/base64"; -import { InvalidJWTString } from "../../domain/models/errors/Pollux"; -import { JWTObject } from "../../domain"; - - -export function decodeJWS(jws: string): JWTObject { - const parts = jws.split("."); - if (parts.length != 3 || parts.at(1) === undefined) { - throw new InvalidJWTString(); - } - const headersEnc = parts[0]; - const headers = base64url.baseDecode(headersEnc); - const payloadEnc = parts[1]; - const payload = base64url.baseDecode(payloadEnc); - return { - header: JSON.parse(Buffer.from(headers).toString()), - payload: JSON.parse(Buffer.from(payload).toString()), - signature: parts[2], - data: `${parts[0]}.${parts[1]}`, - } -} \ No newline at end of file diff --git a/src/pollux/utils/jwt/JWTCore.ts b/src/pollux/utils/jwt/JWTCore.ts index 3323a04ca..b1294ca1c 100644 --- a/src/pollux/utils/jwt/JWTCore.ts +++ b/src/pollux/utils/jwt/JWTCore.ts @@ -1,9 +1,24 @@ import * as didResolver from "did-resolver"; import { base64url } from "multiformats/bases/base64"; import { base58btc } from 'multiformats/bases/base58'; -import { Castor, AlsoKnownAs, Controller, VerificationMethods, Services, PublicKey, PrivateKey, Signer, Hasher, Verifier, Curve, Apollo, KeyProperties, KeyTypes } from "../../../domain"; import { defaultHashConfig, defaultSaltGen } from "./config"; +// TODO shouldnt be importing from castor import { VerificationKeyType } from "../../../castor/types"; +import { Castor, + AlsoKnownAs, + Controller, + VerificationMethods, + Services, + PublicKey, + PrivateKey, + Signer, + Hasher, + Verifier, + Curve, + Apollo, + KeyProperties, + KeyTypes +} from "../../../domain"; /** diff --git a/src/utils/guards.ts b/src/utils/guards.ts index de1237d2c..a787586eb 100644 --- a/src/utils/guards.ts +++ b/src/utils/guards.ts @@ -1,4 +1,5 @@ -import { JsonObj, Nil } from "./types"; +import { ExpectError } from "../domain/models/errors/Common"; +import { Ctor, JsonObj, Nil } from "./types"; /** * isNullish @@ -25,7 +26,7 @@ export const notNil = (value: T | Nil): value is T => !isNil(value); * @see isString * @see isArray */ -export const isEmpty = (value: unknown) => { +export const isEmpty = (value: unknown): value is Nil => { if (isString(value) || isArray(value)) { return value.length === 0; } @@ -91,13 +92,35 @@ export function asArray(items: T | T[] | Nil, guard?: (item: unknown) => item } export const asJsonObj = (value: unknown): JsonObj => { - if (isObject(value)) { - return value; - } - if (isString(value)) { return JSON.parse(value); } + if (isObject(value)) { + return value; + } + return {}; }; + +/** + * expect + * assert a value is notNil and return the value typed as such + * panic otherwise + * + * @param value - the value to check + * @param error? - custom error + */ +export function expect(value: T, error?: string | Ctor | Error): Exclude { + if (isNil(value)) { + if (error instanceof Error) + throw error; + + if (typeof error === "function") + throw new error(); + + throw new ExpectError(error); + } + + return value as Exclude; +} diff --git a/src/utils/tasks.ts b/src/utils/tasks.ts new file mode 100644 index 000000000..e979ea968 --- /dev/null +++ b/src/utils/tasks.ts @@ -0,0 +1,165 @@ +import * as Domain from "../domain"; +import { expect } from "./guards"; +import { JsonObj } from "./types"; + +/** + * + * args constructor parameter is mandatory if Args type given + * args constructor parameter is optional if no Args type given + */ +export abstract class Task { + protected readonly args: Args; + + /** + * + * @param {Args} args + */ + constructor(...args: (unknown extends Args ? [] : [Args])); + constructor(args?: Args) { + this.args = args ?? {} as Args; + } + + abstract run(ctx?: Task.Context): Promise; + + // return loggable information + log(): unknown { + return this.args; + } +} + +export namespace Task { + + // ================================ + // === Context === + // ================================ + + export namespace Context { + export type Options = Config & Deps; + + export interface Config { + logger?: ILogger; + logLevel?: LogLevel; + } + + // dependencies + export interface Deps { + Api?: Domain.Api; + Apollo?: Domain.Apollo; + Castor?: Domain.Castor; + Mercury?: Domain.Mercury; + Pollux?: Domain.Pollux; + Pluto?: Domain.Pluto; + Seed?: Domain.Seed; + } + } + + /** + * + */ + export class Context { + private readonly logger: ILogger; + + constructor( + private readonly opts: Context.Options & T + ) { + this.logger = opts.logger ?? new ConsoleLogger(opts.logLevel); + } + + get Api(): Domain.Api { return this.getProp("Api"); } + get Apollo(): Domain.Apollo { return this.getProp("Apollo"); } + get Castor(): Domain.Castor { return this.getProp("Castor"); } + get Mercury(): Domain.Mercury { return this.getProp("Mercury"); } + get Pollux(): Domain.Pollux { return this.getProp("Pollux"); } + get Pluto(): Domain.Pluto { return this.getProp("Pluto"); } + get Seed(): Domain.Seed { return this.getProp("Seed"); } + + protected getProp(key: K) { + const prop = expect(this.opts[key], `Context missing prop: ${key.toString()}`); + return prop; + } + + async run(task: Task): Promise { + const taskName = task.constructor.name; + + try { + this.logger.debug(`${taskName}: Run`, task.log()); + return await task.run(this); + } + catch (err) { + this.logger.warn(`Fail: ${taskName}`, task.log(), err); + throw err; + } + } + + } + + // ================================ + // === Logger === + // ================================ + + interface ILogger { + debug(message: string, ...params: any[]): void; + info(message: string, ...params: any[]): void; + warn(message: string, ...params: any[]): void; + error(message: string, ...params: any[]): void; + } + + type LogLevel = "debug" | "info" | "warn" | "error" | "none"; + + class ConsoleLogger implements ILogger { + private level: number; + + constructor(logLevel?: LogLevel) { + this.level = this.getLogLevel(logLevel ?? "error"); + } + + getLogLevel(level: LogLevel): number { + switch (level) { + case "none": return 1; + case "debug": return 2; + case "info": return 3; + case "warn": return 4; + case "error": return 5; + } + } + + debug(message: string, ...params: any[]) { + this.log("debug", message, ...params); + } + + info(message: string, ...params: any[]) { + this.log("info", message, ...params); + } + + warn(message: string, ...params: any[]) { + this.log("warn", message, ...params); + } + + error(message: string, ...params: any[]) { + this.log("error", message, ...params); + } + + private log(level: LogLevel, message: string, ...params: any[]) { + const logLevel = this.getLogLevel(level); + + if (logLevel >= this.level) { + const item = { + level, + // identifier: this.identifier ?? "", + time: Date.now(), + message, + // params + }; + + if (params.length > 0) { + Object.assign(item, { params }); + } + + const output = JSON.stringify(item, null, 2); + // const output = JSON.stringify(item); + console.log(output); + } + } + } + +} diff --git a/src/utils/types.ts b/src/utils/types.ts index 2543ab565..4712251c1 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -1,3 +1,10 @@ +/** + * Constructor for T + */ +export interface Ctor { + new(...args: any[]): T; +} + /** * no value shorthand */ diff --git a/tests/agent/Agent.ConnectionsManager.test.ts b/tests/agent/Agent.ConnectionsManager.test.ts index 0dcd7225c..e4697c57e 100644 --- a/tests/agent/Agent.ConnectionsManager.test.ts +++ b/tests/agent/Agent.ConnectionsManager.test.ts @@ -4,10 +4,9 @@ import chai from "chai"; import chaiAsPromised from "chai-as-promised"; import SinonChai from "sinon-chai"; -import { Apollo, BasicMediatorHandler, Castor, ConnectionsManager, MediatorStore, Pluto } from "../../src"; +import { Apollo, BasicMediatorHandler, Castor, ConnectionsManager, MediatorStore, Pluto, Pollux } from "../../src"; import { Curve, KeyTypes, Mercury, Service, ServiceEndpoint } from "../../src/domain"; import { MercuryStub } from "./mocks/MercuryMock"; -import { AgentCredentials } from "../../src/edge-agent/Agent.Credentials"; import { AgentOptions } from "../../src/edge-agent/types"; chai.use(SinonChai); @@ -19,8 +18,6 @@ const mercury: Mercury = new MercuryStub(); const apollo = new Apollo(); const castor = new Castor(apollo) const pluto: Pluto = null as any; -const agentCredentials: AgentCredentials = null as any; - async function createBasicMediationHandler( ConnectionsManager: any, @@ -50,11 +47,13 @@ async function createBasicMediationHandler( routingDID: mediatorDID, mediatorDID: mediatorDID } + + const pollux: Pollux = null as any; const manager = new ConnectionsManager( castor, mercury, pluto, - agentCredentials, + pollux, handler, [], options diff --git a/tests/agent/Agent.test.ts b/tests/agent/Agent.test.ts index 1b927e73c..7136e7fe6 100644 --- a/tests/agent/Agent.test.ts +++ b/tests/agent/Agent.test.ts @@ -5,12 +5,12 @@ import chai from "chai"; import chaiAsPromised from "chai-as-promised"; import * as sinon from "sinon"; import SinonChai from "sinon-chai"; -import Agent from "../../src/edge-agent/Agent"; -import Mercury from "../../src/mercury/Mercury"; import * as UUIDLib from "@stablelib/uuid"; + +import Agent from "../../src/edge-agent/didcomm/Agent"; +import Mercury from "../../src/mercury/Mercury"; import Apollo from "../../src/apollo/Apollo"; import { CastorMock } from "./mocks/CastorMock"; -import { ConnectionsManagerMock } from "./mocks/ConnectionManagerMock"; import * as Fixtures from "../fixtures"; import { Api, @@ -47,13 +47,12 @@ import InMemoryStore from "../fixtures/inmemory"; import { ApiResponse, Pluto as IPluto } from "../../src/domain"; import { Pluto } from "../../src/pluto/Pluto"; import { RevocationNotification } from "../../src/edge-agent/protocols/revocation/RevocationNotfiication"; -import { AgentCredentials } from "../../src/edge-agent/Agent.Credentials"; import { BasicMediatorHandler, Castor, Store } from "../../src"; import { randomUUID } from "crypto"; -import { AgentDIDHigherFunctions } from "../../src/edge-agent/Agent.DIDHigherFunctions"; import { JWT } from "../../src/pollux/utils/JWT"; + chai.use(SinonChai); chai.use(chaiAsPromised); const expect = chai.expect; @@ -98,64 +97,49 @@ describe("Agent Tests", () => { storage: InMemoryStore, password: Buffer.from("demoapp").toString("hex") }); + pluto = new Pluto(store, apollo); const mercury = new Mercury(castor, didProtocol, httpManager); - const polluxInstance = new Pollux(apollo, castor); - const handler = new BasicMediatorHandler(DID.fromString("did:peer:123456"), mercury, pluto); 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]) }; - const didHigherFunctions = new AgentDIDHigherFunctions( - apollo, - castor, - pluto, - handler, - seed - ); - - const agentCredentials = new AgentCredentials( + agent = Agent.initialize({ + mediatorDID: DID.from("did:peer:2.Ez6LSghwSE437wnDE1pt3X6hVDUQzSjsHzinpX3XFvMjRAm7y.Vz6Mkhh1e5CEYYq6JBUcTZ6Cp2ranCWRrv7Yax3Le4N59R6dd.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6Imh0dHA6Ly8xOTIuMTY4LjEuNDQ6ODA4MCIsImEiOlsiZGlkY29tbS92MiJdfX0.SeyJ0IjoiZG0iLCJzIjp7InVyaSI6IndzOi8vMTkyLjE2OC4xLjQ0OjgwODAvd3MiLCJhIjpbImRpZGNvbW0vdjIiXX19"), apollo, castor, pluto, - polluxInstance, - seed, - mercury, - didHigherFunctions - ); - - const connectionsManager = ConnectionsManagerMock.buildMock({ - castor, mercury, - pluto, - agentCredentials, - options: { - experiments: { - liveMode: false - } - } + seed, }); - agent = Agent.instanceFromConnectionManager( - apollo, - castor, - pluto, - mercury, - connectionsManager, - seed, - undefined, - { - experiments: { - liveMode: false - } - } - ); + sandbox.stub(agent.connectionManager, "startMediator").resolves(); + sandbox.stub(agent.connectionManager, "startFetchingMessages").resolves(); + (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"), + }; - await polluxInstance.start(); + // instanceFromConnectionManager( + // apollo, + // castor, + // pluto, + // mercury, + // connectionsManager, + // seed, + // undefined, + // { + // experiments: { + // liveMode: false + // } + // } + // ); - pollux = (agent as any).pollux as Pollux; + await polluxInstance.start(); + pollux = agent.pollux; }); describe("Integration Tests", () => { @@ -165,12 +149,8 @@ describe("Agent Tests", () => { it("As a developer when a peerDID is created and we have specified to updateKeyList the services are correctly added and updateKeyList is called correctly.", async () => { - const didHigherFunctions = (agent as any).agentDIDHigherFunctions; const storePeerDID = sandbox.stub(pluto, "storeDID").resolves(); - const updateKeyList = sandbox.stub( - didHigherFunctions.mediationHandler, - "updateKeyListWithDIDs" - ); + const updateKeyList = sandbox.stub(agent.mediationHandler, "updateKeyListWithDIDs"); const createPeerDID = sandbox.stub(castor, "createPeerDID"); const peerDID = await agent.createNewPeerDID([], true); @@ -207,9 +187,7 @@ describe("Agent Tests", () => { it("As a developer with a valid invitationMessage I will be sending a Handshake request with the correct information and store the didPair in pluto right after.", async () => { - const agentInvitations = (agent as any).agentInvitations; - const agentInvitationsConnection = agentInvitations.connection; - const didHigherFunctions = (agent as any).agentDIDHigherFunctions; + const connectionManager = agent.connectionManager; const did = DID.fromString( "did:peer:2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOnsidXJpIjoiaHR0cHM6Ly9tZWRpYXRvci5yb290c2lkLmNsb3VkIiwiYSI6WyJkaWRjb21tL3YyIl19fQ" @@ -217,18 +195,9 @@ describe("Agent Tests", () => { const validOOB = "https://my.domain.com/path?_oob=eyJpZCI6Ijg5NWYzMWZhLTIyNWUtNDRlNi1hNzkyLWFhN2E0OGY1MjgzYiIsInR5cGUiOiJodHRwczovL2RpZGNvbW0ub3JnL291dC1vZi1iYW5kLzIuMC9pbnZpdGF0aW9uIiwiZnJvbSI6ImRpZDpwZWVyOjIuRXo2TFNlenlrY0JqTUtnR1BFRGg0NHBDOFFmdTdjQ3pKb3NWdVY0anA2eDVZNUJITC5WejZNa3dSSnQxU21acDNhRERoTFVuNGZLMzNtOExMWlhXOTJYVDh2clVIdTR1cEE2LlNleUowSWpvaVpHMGlMQ0p6SWpvaWFIUjBjSE02THk5ck9ITXRaR1YyTG1GMFlXeGhjSEpwYzIwdWFXOHZjSEpwYzIwdFlXZGxiblF2Wkdsa1kyOXRiU0lzSW5JaU9sdGRMQ0poSWpwYkltUnBaR052YlcwdmRqSWlYWDAiLCJib2R5Ijp7ImdvYWxfY29kZSI6ImlvLmF0YWxhcHJpc20uY29ubmVjdCIsImdvYWwiOiJFc3RhYmxpc2ggYSB0cnVzdCBjb25uZWN0aW9uIGJldHdlZW4gdHdvIHBlZXJzIHVzaW5nIHRoZSBwcm90b2NvbCAnaHR0cHM6Ly9hdGFsYXByaXNtLmlvL21lcmN1cnkvY29ubmVjdGlvbnMvMS4wL3JlcXVlc3QnIiwiYWNjZXB0IjpbXX19"; - const createPeerDID = sandbox.stub( - didHigherFunctions, - "createNewPeerDID" - ); - const sendMessage = sandbox.stub( - agentInvitationsConnection, - "sendMessage" - ); - const addConnection = sandbox.stub( - agentInvitationsConnection, - "addConnection" - ); + const createPeerDID = sandbox.stub(agent, "createNewPeerDID"); + const sendMessage = sandbox.stub(connectionManager, "sendMessage"); + const addConnection = sandbox.stub(connectionManager, "addConnection"); sandbox.stub(UUIDLib, "uuid").returns("123456-123456-12356-123456"); @@ -253,9 +222,7 @@ describe("Agent Tests", () => { }); it("As a developer with a valid invitationMessage I will be sending a presentation with the correct information, but will fail as it is expired.", async () => { - const agentInvitations = (agent as any).agentInvitations; - const agentInvitationsConnection = agentInvitations.connection; - const didHigherFunctions = (agent as any).agentDIDHigherFunctions; + const connectionManager = agent.connectionManager; const did = DID.fromString( "did:peer:2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOnsidXJpIjoiaHR0cHM6Ly9tZWRpYXRvci5yb290c2lkLmNsb3VkIiwiYSI6WyJkaWRjb21tL3YyIl19fQ" @@ -263,18 +230,9 @@ describe("Agent Tests", () => { const validOOB = "https://my.domain.com/path?_oob=eyJpZCI6IjViMjUwMjIzLWExNDItNDRmYi1hOWJkLWU1MjBlNGI0ZjQzMiIsInR5cGUiOiJodHRwczovL2RpZGNvbW0ub3JnL291dC1vZi1iYW5kLzIuMC9pbnZpdGF0aW9uIiwiZnJvbSI6ImRpZDpwZWVyOjIuRXo2TFNkV0hWQ1BFOHc0NWZETjM4aUh0ZFJ6WGkyTFNqQmRSUjRGTmNOUm12VkNKcy5WejZNa2Z2aUI5S1F1OGlnNVZpeG1HZHM3dmdMNmoyUXNOUGFybkZaanBNQ0E5aHpQLlNleUowSWpvaVpHMGlMQ0p6SWpwN0luVnlhU0k2SW1oMGRIQTZMeTh4T1RJdU1UWTRMakV1TXpjNk9EQTNNQzlrYVdSamIyMXRJaXdpY2lJNlcxMHNJbUVpT2xzaVpHbGtZMjl0YlM5Mk1pSmRmWDAiLCJib2R5Ijp7ImdvYWxfY29kZSI6InByZXNlbnQtdnAiLCJnb2FsIjoiUmVxdWVzdCBwcm9vZiBvZiB2YWNjaW5hdGlvbiBpbmZvcm1hdGlvbiIsImFjY2VwdCI6W119LCJhdHRhY2htZW50cyI6W3siaWQiOiIyYTZmOGM4NS05ZGE3LTRkMjQtOGRhNS0wYzliZDY5ZTBiMDEiLCJtZWRpYV90eXBlIjoiYXBwbGljYXRpb24vanNvbiIsImRhdGEiOnsianNvbiI6eyJpZCI6IjI1NTI5MTBiLWI0NmMtNDM3Yy1hNDdhLTlmODQ5OWI5ZTg0ZiIsInR5cGUiOiJodHRwczovL2RpZGNvbW0uYXRhbGFwcmlzbS5pby9wcmVzZW50LXByb29mLzMuMC9yZXF1ZXN0LXByZXNlbnRhdGlvbiIsImJvZHkiOnsiZ29hbF9jb2RlIjoiUmVxdWVzdCBQcm9vZiBQcmVzZW50YXRpb24iLCJ3aWxsX2NvbmZpcm0iOmZhbHNlLCJwcm9vZl90eXBlcyI6W119LCJhdHRhY2htZW50cyI6W3siaWQiOiJiYWJiNTJmMS05NDUyLTQzOGYtYjk3MC0yZDJjOTFmZTAyNGYiLCJtZWRpYV90eXBlIjoiYXBwbGljYXRpb24vanNvbiIsImRhdGEiOnsianNvbiI6eyJvcHRpb25zIjp7ImNoYWxsZW5nZSI6IjExYzkxNDkzLTAxYjMtNGM0ZC1hYzM2LWIzMzZiYWI1YmRkZiIsImRvbWFpbiI6Imh0dHBzOi8vcHJpc20tdmVyaWZpZXIuY29tIn0sInByZXNlbnRhdGlvbl9kZWZpbml0aW9uIjp7ImlkIjoiMGNmMzQ2ZDItYWY1Ny00Y2E1LTg2Y2EtYTA1NTE1NjZlYzZmIiwiaW5wdXRfZGVzY3JpcHRvcnMiOltdfX19LCJmb3JtYXQiOiJwcmlzbS9qd3QifV0sInRoaWQiOiI1YjI1MDIyMy1hMTQyLTQ0ZmItYTliZC1lNTIwZTRiNGY0MzIiLCJmcm9tIjoiZGlkOnBlZXI6Mi5FejZMU2RXSFZDUEU4dzQ1ZkROMzhpSHRkUnpYaTJMU2pCZFJSNEZOY05SbXZWQ0pzLlZ6Nk1rZnZpQjlLUXU4aWc1Vml4bUdkczd2Z0w2ajJRc05QYXJuRlpqcE1DQTloelAuU2V5SjBJam9pWkcwaUxDSnpJanA3SW5WeWFTSTZJbWgwZEhBNkx5OHhPVEl1TVRZNExqRXVNemM2T0RBM01DOWthV1JqYjIxdElpd2ljaUk2VzEwc0ltRWlPbHNpWkdsa1kyOXRiUzkyTWlKZGZYMCJ9fX1dLCJjcmVhdGVkX3RpbWUiOjE3MjQzMzkxNDQsImV4cGlyZXNfdGltZSI6MTcyNDMzOTQ0NH0"; - const createPeerDID = sandbox.stub( - didHigherFunctions, - "createNewPeerDID" - ); - const sendMessage = sandbox.stub( - agentInvitationsConnection, - "sendMessage" - ); - const addConnection = sandbox.stub( - agentInvitationsConnection, - "addConnection" - ); + const createPeerDID = sandbox.stub(agent, "createNewPeerDID"); + const sendMessage = sandbox.stub(connectionManager, "sendMessage"); + const addConnection = sandbox.stub(connectionManager, "addConnection"); sandbox.stub(UUIDLib, "uuid").returns("123456-123456-12356-123456"); diff --git a/tests/agent/mocks/ConnectionManagerMock.ts b/tests/agent/mocks/ConnectionManagerMock.ts deleted file mode 100644 index f21a0d1db..000000000 --- a/tests/agent/mocks/ConnectionManagerMock.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - AgentCredentials, - AgentOptions, - ConnectionsManager as ConnectionsManagerClass, - EventCallback, - MediatorHandler, -} from "../../../src/edge-agent/types"; -import { Castor } from "../../../src/domain/buildingBlocks/Castor"; -import { Mercury } from "../../../src/domain/buildingBlocks/Mercury"; -import { Pluto } from "../../../src/domain/buildingBlocks/Pluto"; -import { DIDPair } from "../../../src/domain/models/DIDPair"; -import { CancellableTask } from "../../../src/edge-agent/helpers/Task"; -import { DID, Mediator, Message, Pollux } from "../../../src/domain"; -import { AgentMessageEvents } from "../../../src/edge-agent/Agent.MessageEvents"; -import { ConnectionsManager } from "../../../src"; - - - -type ConnectionMockConstructor = { - castor: Castor, - mercury: Mercury, - pluto: Pluto, - agentCredentials: AgentCredentials, - mediationHandler: MediatorHandler, - pairings?: DIDPair[], - options?: AgentOptions -} - - -export class ConnectionsManagerMock implements ConnectionsManagerClass { - private manager: ConnectionsManagerClass; - public options?: AgentOptions - - constructor( - params: ConnectionMockConstructor - ) { - const { castor, mercury, pluto, agentCredentials, options } = params - - this.castor = castor; - this.mercury = mercury; - this.pluto = pluto; - this.agentCredentials = agentCredentials; - this.options = options; - - const connManager = new ConnectionsManager( - this.castor, - this.mercury, - this.pluto, - this.agentCredentials, - this.mediationHandler, - [], - options - ) - this.manager = connManager; - this.mediationHandler = this.manager.mediationHandler; - } - - - static buildMock(params: Partial): ConnectionsManagerMock { - const mediationHandler: MediatorHandler = { - registerMessagesAsRead: async () => { }, - updateKeyListWithDIDs: async () => { }, - mediator: { - mediatorDID: new DID( - "did", - "peer", - "2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOnsidXJpIjoiaHR0cHM6Ly9tZWRpYXRvci5yb290c2lkLmNsb3VkIiwiYSI6WyJkaWRjb21tL3YyIl19fQ" - ), - hostDID: new DID( - "did", - "peer", - "2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOnsidXJpIjoiaHR0cHM6Ly9tZWRpYXRvci5yb290c2lkLmNsb3VkIiwiYSI6WyJkaWRjb21tL3YyIl19fQ" - ), - routingDID: new DID( - "did", - "peer", - "2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOnsidXJpIjoiaHR0cHM6Ly9tZWRpYXRvci5yb290c2lkLmNsb3VkIiwiYSI6WyJkaWRjb21tL3YyIl19fQ" - ), - }, - mediatorDID: new DID( - "did", - "peer", - "2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOnsidXJpIjoiaHR0cHM6Ly9tZWRpYXRvci5yb290c2lkLmNsb3VkIiwiYSI6WyJkaWRjb21tL3YyIl19fQ" - ), - bootRegisteredMediator: function (): Promise { - throw new Error("Mock bootRegisteredMediator Function not implemented."); - }, - achieveMediation: function (host: DID): Promise { - throw new Error("Mock achieveMediation Function not implemented."); - }, - pickupUnreadMessages: function (limit: number): Promise<{ attachmentId: string; message: Message; }[]> { - throw new Error("Mock pickupUnreadMessages Function not implemented."); - }, - listenUnreadMessages: function (signal: AbortSignal, serviceEndpointUri: string, onMessage: (messages: { attachmentId: string; message: Message; }[]) => void | Promise): void { - throw new Error("Function not implemented."); - } - }; - - params.mediationHandler = mediationHandler; - - return new ConnectionsManagerMock( - params as ConnectionMockConstructor - ) - } - - get withWebsocketsExperiment() { - return this.options?.experiments?.liveMode === true - } - - processMessages(messages: { attachmentId: string; message: Message; }[]): Promise { - return this.manager.processMessages(messages) - } - - events = new AgentMessageEvents(); - castor: Castor; - mercury: Mercury; - pluto: Pluto; - agentCredentials: AgentCredentials; - mediationHandler: MediatorHandler = { - registerMessagesAsRead: () => { }, - updateKeyListWithDIDs: () => { }, - mediator: { - mediatorDID: new DID( - "did", - "peer", - "2.Ez6LSms555YhFthn1WV8ciDBpZm86hK9tp83WojJUmxPGk1hZ.Vz6MkmdBjMyB4TS5UbbQw54szm8yvMMf1ftGV2sQVYAxaeWhE.SeyJpZCI6Im5ldy1pZCIsInQiOiJkbSIsInMiOnsidXJpIjoiaHR0cHM6Ly9tZWRpYXRvci5yb290c2lkLmNsb3VkIiwiYSI6WyJkaWRjb21tL3YyIl19fQ" - ), - }, - } as any; - pairings: DIDPair[]; - cancellables: CancellableTask[]; - findIndex(pair: DIDPair) { - return this.pairings.findIndex( - (pairing) => - pair.host === pairing.host && - pair.name === pairing.name && - pair.receiver === pairing.receiver - ); - } - stopFetchingMessages() { - return; - } - async startFetchingMessages() { - return; - } - stopAllEvents(): void { - return; - } - addConnection(paired: DIDPair): Promise { - return Promise.resolve(); - } - removeConnection(pair: DIDPair): Promise { - return Promise.resolve(); - } - awaitMessages(): Promise { - return Promise.resolve([]); - } - awaitMessageResponse(id: string): Promise { - return Promise.resolve(undefined); - } - sendMessage(message: Message): Promise { - return Promise.resolve(undefined); - } - startMediator(): Promise { - return Promise.resolve(); - } - registerMediator(hostDID: DID): Promise { - return Promise.resolve(); - } -} diff --git a/tests/pollux/Bitstring.test.ts b/tests/pollux/Bitstring.test.ts index 5947bc35a..ef69c87b3 100644 --- a/tests/pollux/Bitstring.test.ts +++ b/tests/pollux/Bitstring.test.ts @@ -15,13 +15,12 @@ describe('Bitstring', () => { const buffer = Uint8Array.from([128, 3]); const bitstring = new Bitstring({ buffer }); const validIndexes = [0, 14, 15]; + for (let i = 0; i < 16; i++) { const bitstringIndex = bitstring.get(i); if (validIndexes.includes(i)) { - console.log("Index should be true ", i) expect(bitstringIndex).to.be.true; } else { - console.log("Index should be false ", i) expect(bitstringIndex).to.be.false; } } @@ -31,13 +30,12 @@ describe('Bitstring', () => { const buffer = Uint8Array.from([2, 0]); const bitstring = new Bitstring({ buffer }); const validIndexes = [6]; + for (let i = 0; i < 16; i++) { const bitstringIndex = bitstring.get(i); if (validIndexes.includes(i)) { - console.log("Index should be true ", i) expect(bitstringIndex).to.be.true; } else { - console.log("Index should be false ", i) expect(bitstringIndex).to.be.false; } }