From c8472b4a512b169b5161caeaac13b43ea19df017 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 9 Feb 2023 16:04:11 +0100 Subject: [PATCH 01/22] refactor(proofs)!: generalize proofs api and improve consistency with credentials module Signed-off-by: Timo Glastra --- demo/src/Alice.ts | 5 +- demo/src/Faber.ts | 18 +- package.json | 2 - .../tests/bbs-signatures.e2e.test.ts | 3 - ...proof.credentials.propose-offerBbs.test.ts | 2 +- packages/core/src/agent/AgentModules.ts | 4 +- packages/core/src/agent/BaseAgent.ts | 7 +- .../core/src/agent/__tests__/Agent.test.ts | 11 +- .../src/decorators/attachment/Attachment.ts | 4 +- .../connections/services/ConnectionService.ts | 4 +- .../src/modules/credentials/CredentialsApi.ts | 181 +-- .../credentials/CredentialsApiOptions.ts | 38 +- .../modules/credentials/CredentialsModule.ts | 4 +- .../formats/CredentialFormatService.ts | 84 +- .../formats/CredentialFormatServiceOptions.ts | 45 +- .../IndyCredentialFormatService.test.ts | 12 +- .../JsonLdCredentialFormatService.test.ts | 14 +- .../indy/IndyCredentialFormatService.ts | 117 +- .../formats/indy/models/IndyCredentialInfo.ts | 11 +- .../jsonld/JsonLdCredentialFormatService.ts | 98 +- .../models/CredentialFormatSpec.ts | 6 +- .../credentials/models/CredentialState.ts | 1 + .../protocol/BaseCredentialProtocol.ts | 77 +- .../protocol/CredentialProtocol.ts | 64 +- .../CredentialProtocolOptions.ts | 55 +- .../protocol/v1/V1CredentialProtocol.ts | 124 +- .../V1CredentialProtocolCred.test.ts | 110 +- .../V1CredentialProtocolProposeOffer.test.ts | 40 +- .../v1/messages/V1ProposeCredentialMessage.ts | 8 - .../v2/CredentialFormatCoordinator.ts | 2 +- .../protocol/v2/V2CredentialProtocol.ts | 123 +- .../V2CredentialProtocolCred.test.ts | 101 +- .../V2CredentialProtocolOffer.test.ts | 21 +- .../v2/handlers/V2OfferCredentialHandler.ts | 7 +- .../v2/messages/V2IssueCredentialMessage.ts | 4 +- .../v2/messages/V2ProposeCredentialMessage.ts | 23 +- .../credentials/util/composeAutoAccept.ts | 1 - .../proofs/ProofResponseCoordinator.ts | 93 -- .../core/src/modules/proofs/ProofService.ts | 261 ---- packages/core/src/modules/proofs/ProofsApi.ts | 536 ++++---- .../src/modules/proofs/ProofsApiOptions.ts | 204 ++- .../core/src/modules/proofs/ProofsModule.ts | 70 +- .../src/modules/proofs/ProofsModuleConfig.ts | 30 +- .../proofs/__tests__/ProofsModule.test.ts | 51 +- .../__tests__/ProofsModuleConfig.test.ts | 26 + .../src/modules/proofs/__tests__/fixtures.ts | 30 + .../src/modules/proofs/formats/ProofFormat.ts | 47 +- .../proofs/formats/ProofFormatConstants.ts | 4 - .../proofs/formats/ProofFormatService.ts | 125 +- .../formats/ProofFormatServiceOptions.ts | 110 +- .../errors/InvalidEncodedValueError.ts | 3 + .../errors/MissingIndyProofMessageError.ts | 3 + .../core/src/modules/proofs/formats/index.ts | 9 +- .../proofs/formats/indy/IndyProofFormat.ts | 79 +- .../formats/indy/IndyProofFormatService.ts | 763 +++++------ .../indy/IndyProofFormatsServiceOptions.ts | 38 - .../{ => formats/indy}/__tests__/groupKeys.ts | 8 +- .../formats/indy/__tests__/util.test.ts | 493 ++++++++ .../errors/MissingIndyProofMessageError.ts | 3 - .../proofs/formats/indy/errors/index.ts | 1 - .../src/modules/proofs/formats/indy/index.ts | 4 +- .../indy}/models/PartialProof.ts | 0 .../indy}/models/ProofAttribute.ts | 0 .../formats/indy/models/ProofAttributeInfo.ts | 8 +- .../indy}/models/ProofIdentifier.ts | 0 .../formats/indy/models/ProofPredicateInfo.ts | 17 +- .../formats/indy/models/ProofRequest.ts | 33 +- .../formats/indy/models/RequestedAttribute.ts | 14 +- .../indy/models/RequestedCredentials.ts | 26 +- .../formats/indy/models/RequestedPredicate.ts | 13 +- .../indy}/models/RequestedProof.ts | 0 .../models}/__tests__/ProofRequest.test.ts | 8 +- .../src/modules/proofs/formats/indy/util.ts | 252 ++++ .../formats/models/ProofAttachmentFormat.ts | 7 - .../models/ProofFormatServiceOptions.ts | 64 - .../modules/proofs/formats/models/index.ts | 2 - packages/core/src/modules/proofs/index.ts | 7 +- .../proofs/messages/PresentationAckMessage.ts | 10 - .../core/src/modules/proofs/messages/index.ts | 1 - .../models/GetRequestedCredentialsConfig.ts | 19 - .../modules/proofs/models/ModuleOptions.ts | 22 - .../proofs/models/ProofServiceOptions.ts | 85 -- .../modules/proofs/models/SharedOptions.ts | 62 - .../{ => models}/__tests__/ProofState.test.ts | 2 +- .../core/src/modules/proofs/models/index.ts | 1 - .../proofs/protocol/BaseProofProtocol.ts | 286 +++++ .../modules/proofs/protocol/ProofProtocol.ts | 117 ++ .../proofs/protocol/ProofProtocolOptions.ts | 165 +++ .../core/src/modules/proofs/protocol/index.ts | 4 + .../proofs/protocol/v1/V1ProofProtocol.ts | 1111 +++++++++++++++++ .../proofs/protocol/v1/V1ProofService.ts | 1111 ----------------- .../v1/__tests__/V1ProofProtocol.test.ts} | 127 +- .../__tests__/indy-proof-negotiation.test.ts | 61 +- ...ts => indy-proof-presentation.test.e2e.ts} | 20 +- ...est.ts => indy-proof-proposal.test.e2e.ts} | 5 +- .../v1-connectionless-proofs.e2e.test.ts} | 57 +- ...t.ts => v1-indy-proof-request.e2e.test.ts} | 50 +- .../v1/__tests__/v1-indy-proofs.e2e.test.ts} | 158 +-- .../v1-proofs-auto-accept.e2e.test.ts} | 40 +- .../v1/handlers/V1PresentationAckHandler.ts | 10 +- .../v1/handlers/V1PresentationHandler.ts | 84 +- .../V1PresentationProblemReportHandler.ts | 10 +- .../handlers/V1ProposePresentationHandler.ts | 93 +- .../handlers/V1RequestPresentationHandler.ts | 120 +- .../src/modules/proofs/protocol/v1/index.ts | 2 +- .../v1/messages/V1PresentationMessage.ts | 34 +- .../messages/V1ProposePresentationMessage.ts | 20 +- .../messages/V1RequestPresentationMessage.ts | 58 +- .../v1/models/V1PresentationPreview.ts | 44 +- .../proofs/protocol/v1/models/index.ts | 4 - .../protocol/v2/ProofFormatCoordinator.ts | 520 ++++++++ .../proofs/protocol/v2/V2ProofProtocol.ts | 1069 ++++++++++++++++ .../proofs/protocol/v2/V2ProofService.ts | 953 -------------- .../v2/__tests__/V2ProofProtocol.test.ts} | 145 +-- ...v2-indy-connectionless-proofs.e2e.test.ts} | 62 +- ...t.ts => v2-indy-proof-negotiation.test.ts} | 106 +- ...=> v2-indy-proof-presentation.e2e.test.ts} | 37 +- ....ts => v2-indy-proof-proposal.e2e.test.ts} | 18 +- ...t.ts => v2-indy-proof-request.e2e.test.ts} | 24 +- .../v2-indy-proofs-auto-accept.2e.test.ts} | 56 +- .../v2/__tests__/v2-indy-proofs.e2e.test.ts} | 230 ++-- .../v2/handlers/V2PresentationAckHandler.ts | 10 +- .../v2/handlers/V2PresentationHandler.ts | 61 +- .../V2PresentationProblemReportHandler.ts | 6 +- .../handlers/V2ProposePresentationHandler.ts | 93 +- .../handlers/V2RequestPresentationHandler.ts | 94 +- .../src/modules/proofs/protocol/v2/index.ts | 2 +- .../v2/messages/V2PresentationAckMessage.ts | 6 - .../v2/messages/V2PresentationMessage.ts | 43 +- .../V2PresentationProblemReportMessage.ts | 12 - .../messages/V2ProposalPresentationMessage.ts | 100 -- .../messages/V2ProposePresentationMessage.ts | 62 + .../messages/V2RequestPresentationMessage.ts | 70 +- .../proofs/protocol/v2/messages/index.ts | 2 +- .../proofs/repository/ProofExchangeRecord.ts | 8 + .../modules/proofs/utils/composeAutoAccept.ts | 11 + .../src/modules/vc/W3cCredentialService.ts | 11 +- .../vc/__tests__/W3cCredentialService.test.ts | 3 - .../vc/__tests__/contexts/citizenship_v2.ts | 45 + .../modules/vc/__tests__/contexts/index.ts | 2 + .../vc/__tests__/contexts/submission.ts | 15 + .../vc/__tests__/contexts/vaccination_v2.ts | 88 ++ .../modules/vc/__tests__/documentLoader.ts | 14 +- .../core/src/modules/vc/__tests__/fixtures.ts | 30 + packages/core/src/modules/vc/constants.ts | 1 + .../vc/models/W3cCredentialServiceOptions.ts | 2 - .../utils/__tests__/indyProofRequest.test.ts | 81 -- packages/core/src/utils/index.ts | 1 + packages/core/src/utils/indyProofRequest.ts | 2 +- packages/core/tests/helpers.ts | 145 +-- packages/core/tests/logger.ts | 55 +- packages/core/tests/oob.test.ts | 4 +- .../core/tests/proofs-sub-protocol.test.ts | 36 +- packages/openid4vc-client/tests/fixtures.ts | 2 +- .../tests/openid4vc-client.e2e.test.ts | 12 +- tests/e2e-test.ts | 25 +- yarn.lock | 5 - 157 files changed, 6989 insertions(+), 6256 deletions(-) rename packages/core/src/modules/credentials/{ => protocol}/CredentialProtocolOptions.ts (69%) delete mode 100644 packages/core/src/modules/proofs/ProofResponseCoordinator.ts delete mode 100644 packages/core/src/modules/proofs/ProofService.ts create mode 100644 packages/core/src/modules/proofs/__tests__/ProofsModuleConfig.test.ts delete mode 100644 packages/core/src/modules/proofs/formats/ProofFormatConstants.ts create mode 100644 packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts create mode 100644 packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts rename packages/core/src/modules/proofs/{ => formats/indy}/__tests__/groupKeys.ts (79%) create mode 100644 packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts rename packages/core/src/modules/proofs/{protocol/v1 => formats/indy}/models/PartialProof.ts (100%) rename packages/core/src/modules/proofs/{protocol/v1 => formats/indy}/models/ProofAttribute.ts (100%) rename packages/core/src/modules/proofs/{protocol/v1 => formats/indy}/models/ProofIdentifier.ts (100%) rename packages/core/src/modules/proofs/{protocol/v1 => formats/indy}/models/RequestedProof.ts (100%) rename packages/core/src/modules/proofs/{ => formats/indy/models}/__tests__/ProofRequest.test.ts (87%) create mode 100644 packages/core/src/modules/proofs/formats/indy/util.ts delete mode 100644 packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts delete mode 100644 packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts delete mode 100644 packages/core/src/modules/proofs/formats/models/index.ts delete mode 100644 packages/core/src/modules/proofs/messages/PresentationAckMessage.ts delete mode 100644 packages/core/src/modules/proofs/messages/index.ts delete mode 100644 packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts delete mode 100644 packages/core/src/modules/proofs/models/ModuleOptions.ts delete mode 100644 packages/core/src/modules/proofs/models/ProofServiceOptions.ts delete mode 100644 packages/core/src/modules/proofs/models/SharedOptions.ts rename packages/core/src/modules/proofs/{ => models}/__tests__/ProofState.test.ts (92%) create mode 100644 packages/core/src/modules/proofs/protocol/BaseProofProtocol.ts create mode 100644 packages/core/src/modules/proofs/protocol/ProofProtocol.ts create mode 100644 packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts create mode 100644 packages/core/src/modules/proofs/protocol/v1/V1ProofProtocol.ts delete mode 100644 packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts rename packages/core/src/modules/proofs/{__tests__/V1ProofService.test.ts => protocol/v1/__tests__/V1ProofProtocol.test.ts} (63%) rename packages/core/src/modules/proofs/protocol/v1/__tests__/{indy-proof-presentation.test.ts => indy-proof-presentation.test.e2e.ts} (93%) rename packages/core/src/modules/proofs/protocol/v1/__tests__/{indy-proof-proposal.test.ts => indy-proof-proposal.test.e2e.ts} (95%) rename packages/core/{tests/v1-connectionless-proofs.test.ts => src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts} (89%) rename packages/core/src/modules/proofs/protocol/v1/__tests__/{indy-proof-request.test.ts => v1-indy-proof-request.e2e.test.ts} (70%) rename packages/core/{tests/v1-indy-proofs.test.ts => src/modules/proofs/protocol/v1/__tests__/v1-indy-proofs.e2e.test.ts} (81%) rename packages/core/{tests/v1-proofs-auto-accept.test.ts => src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts} (82%) create mode 100644 packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts delete mode 100644 packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts rename packages/core/src/modules/proofs/{__tests__/V2ProofService.test.ts => protocol/v2/__tests__/V2ProofProtocol.test.ts} (61%) rename packages/core/{tests/v2-connectionless-proofs.test.ts => src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts} (88%) rename packages/core/src/modules/proofs/protocol/v2/__tests__/{indy-proof-negotiation.test.ts => v2-indy-proof-negotiation.test.ts} (84%) rename packages/core/src/modules/proofs/protocol/v2/__tests__/{indy-proof-presentation.test.ts => v2-indy-proof-presentation.e2e.test.ts} (89%) rename packages/core/src/modules/proofs/protocol/v2/__tests__/{indy-proof-proposal.test.ts => v2-indy-proof-proposal.e2e.test.ts} (84%) rename packages/core/src/modules/proofs/protocol/v2/__tests__/{indy-proof-request.test.ts => v2-indy-proof-request.e2e.test.ts} (87%) rename packages/core/{tests/v2-proofs-auto-accept.test.ts => src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts} (79%) rename packages/core/{tests/v2-indy-proofs.test.ts => src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts} (77%) delete mode 100644 packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts create mode 100644 packages/core/src/modules/proofs/protocol/v2/messages/V2ProposePresentationMessage.ts create mode 100644 packages/core/src/modules/proofs/utils/composeAutoAccept.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/citizenship_v2.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/submission.ts create mode 100644 packages/core/src/modules/vc/__tests__/contexts/vaccination_v2.ts delete mode 100644 packages/core/src/utils/__tests__/indyProofRequest.test.ts diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index 252c04c632..aa705ca7a4 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -52,11 +52,8 @@ export class Alice extends BaseAgent { } public async acceptProofRequest(proofRecord: ProofExchangeRecord) { - const requestedCredentials = await this.agent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await this.agent.proofs.selectCredentialsForRequest({ proofRecordId: proofRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await this.agent.proofs.acceptRequest({ diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index 267349b785..a19906d0fa 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -2,13 +2,7 @@ import type { ConnectionRecord, ConnectionStateChangedEvent } from '@aries-frame import type { CredDef, Schema } from 'indy-sdk' import type BottomBar from 'inquirer/lib/ui/bottom-bar' -import { - AttributeFilter, - ProofAttributeInfo, - utils, - V1CredentialPreview, - ConnectionEventTypes, -} from '@aries-framework/core' +import { utils, V1CredentialPreview, ConnectionEventTypes } from '@aries-framework/core' import { ui } from 'inquirer' import { BaseAgent } from './BaseAgent' @@ -171,15 +165,16 @@ export class Faber extends BaseAgent { private async newProofAttribute() { await this.printProofFlow(greenText(`Creating new proof attribute for 'name' ...\n`)) const proofAttribute = { - name: new ProofAttributeInfo({ + name: { name: 'name', restrictions: [ - new AttributeFilter({ + { credentialDefinitionId: this.credentialDefinition?.id, - }), + }, ], - }), + }, } + return proofAttribute } @@ -195,7 +190,6 @@ export class Faber extends BaseAgent { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: proofAttribute, }, }, diff --git a/package.json b/package.json index 24f487b9a2..582f91a77e 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,6 @@ "test": "jest", "lint": "eslint --ignore-path .gitignore .", "validate": "yarn lint && yarn check-types && yarn check-format", - "prepare": "husky install", "run-mediator": "ts-node ./samples/mediator.ts", "next-version-bump": "ts-node ./scripts/get-next-bump.ts" }, @@ -48,7 +47,6 @@ "eslint-plugin-import": "^2.23.4", "eslint-plugin-prettier": "^3.4.0", "express": "^4.17.1", - "husky": "^7.0.1", "indy-sdk": "^1.16.0-dev-1636", "jest": "^27.0.4", "lerna": "^4.0.0", diff --git a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index 8e579225fe..936dfbe22e 100644 --- a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -252,10 +252,7 @@ describeSkipNode17And18('BBS W3cCredentialService', () => { const result = await w3cCredentialService.verifyPresentation(agentContext, { presentation: vp, - proofType: 'Ed25519Signature2018', challenge: 'e950bfe5-d7ec-4303-ad61-6983fb976ac9', - verificationMethod: - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', }) expect(result.verified).toBe(true) diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index 997c73c18b..a22408ce87 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -87,7 +87,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') - const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnection.id, protocolVersion: 'v2', credentialFormats: { diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 3f9512bdba..20f0d6cbb7 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -36,9 +36,11 @@ export type AgentModulesInput = Partial & ModulesMap * Defines the input type for the default agent modules. This is overwritten as we * want the input type to allow for generics to be passed in for the credentials module. */ -export type DefaultAgentModulesInput = Omit & { +export type DefaultAgentModulesInput = Omit & { // eslint-disable-next-line @typescript-eslint/no-explicit-any credentials: CredentialsModule + // eslint-disable-next-line @typescript-eslint/no-explicit-any + proofs: ProofsModule } /** diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 5d47157f59..704b2b5d98 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -3,6 +3,7 @@ import type { AgentApi, EmptyModuleMap, ModulesMap, WithoutDefaultModules, Custo import type { TransportSession } from './TransportService' import type { Logger } from '../logger' import type { CredentialsModule } from '../modules/credentials' +import type { ProofsModule } from '../modules/proofs' import type { DependencyManager } from '../plugins' import { AriesFrameworkError } from '../error' @@ -14,7 +15,7 @@ import { DiscoverFeaturesApi } from '../modules/discover-features' import { GenericRecordsApi } from '../modules/generic-records' import { LedgerApi } from '../modules/ledger' import { OutOfBandApi } from '../modules/oob' -import { ProofsApi } from '../modules/proofs/ProofsApi' +import { ProofsApi } from '../modules/proofs' import { MediatorApi, RecipientApi } from '../modules/routing' import { StorageUpdateService } from '../storage' import { UpdateAssistant } from '../storage/migration/UpdateAssistant' @@ -44,7 +45,7 @@ export abstract class BaseAgent - public readonly proofs: ProofsApi + public readonly proofs: CustomOrDefaultApi public readonly mediator: MediatorApi public readonly mediationRecipient: RecipientApi public readonly basicMessages: BasicMessagesApi @@ -88,7 +89,7 @@ export abstract class BaseAgent - this.proofs = this.dependencyManager.resolve(ProofsApi) + this.proofs = this.dependencyManager.resolve(ProofsApi) as CustomOrDefaultApi this.mediator = this.dependencyManager.resolve(MediatorApi) this.mediationRecipient = this.dependencyManager.resolve(RecipientApi) this.basicMessages = this.dependencyManager.resolve(BasicMessagesApi) diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 2eca3350be..31ff626e32 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -16,8 +16,8 @@ import { IndyLedgerService } from '../../modules/ledger' import { LedgerApi } from '../../modules/ledger/LedgerApi' import { ProofRepository } from '../../modules/proofs' import { ProofsApi } from '../../modules/proofs/ProofsApi' -import { V1ProofService } from '../../modules/proofs/protocol/v1' -import { V2ProofService } from '../../modules/proofs/protocol/v2' +import { V1ProofProtocol } from '../../modules/proofs/protocol/v1' +import { V2ProofProtocol } from '../../modules/proofs/protocol/v2' import { MediationRecipientService, MediationRepository, @@ -161,8 +161,6 @@ describe('Agent', () => { expect(container.resolve(ConnectionRepository)).toBeInstanceOf(ConnectionRepository) expect(container.resolve(TrustPingService)).toBeInstanceOf(TrustPingService) - expect(container.resolve(V1ProofService)).toBeInstanceOf(V1ProofService) - expect(container.resolve(V2ProofService)).toBeInstanceOf(V2ProofService) expect(container.resolve(ProofsApi)).toBeInstanceOf(ProofsApi) expect(container.resolve(ProofRepository)).toBeInstanceOf(ProofRepository) @@ -204,8 +202,6 @@ describe('Agent', () => { expect(container.resolve(ConnectionRepository)).toBe(container.resolve(ConnectionRepository)) expect(container.resolve(TrustPingService)).toBe(container.resolve(TrustPingService)) - expect(container.resolve(V1ProofService)).toBe(container.resolve(V1ProofService)) - expect(container.resolve(V2ProofService)).toBe(container.resolve(V2ProofService)) expect(container.resolve(ProofsApi)).toBe(container.resolve(ProofsApi)) expect(container.resolve(ProofRepository)).toBe(container.resolve(ProofRepository)) @@ -263,10 +259,11 @@ describe('Agent', () => { 'https://didcomm.org/messagepickup/2.0', 'https://didcomm.org/out-of-band/1.1', 'https://didcomm.org/present-proof/1.0', + 'https://didcomm.org/present-proof/2.0', 'https://didcomm.org/revocation_notification/1.0', 'https://didcomm.org/revocation_notification/2.0', ]) ) - expect(protocols.length).toEqual(14) + expect(protocols.length).toEqual(15) }) }) diff --git a/packages/core/src/decorators/attachment/Attachment.ts b/packages/core/src/decorators/attachment/Attachment.ts index b39fa52d8d..50a91e8edb 100644 --- a/packages/core/src/decorators/attachment/Attachment.ts +++ b/packages/core/src/decorators/attachment/Attachment.ts @@ -25,7 +25,7 @@ export interface AttachmentOptions { mimeType?: string lastmodTime?: Date byteCount?: number - data: AttachmentData + data: AttachmentDataOptions } export interface AttachmentDataOptions { @@ -97,7 +97,7 @@ export class Attachment { this.mimeType = options.mimeType this.lastmodTime = options.lastmodTime this.byteCount = options.byteCount - this.data = options.data + this.data = new AttachmentData(options.data) } } diff --git a/packages/core/src/modules/connections/services/ConnectionService.ts b/packages/core/src/modules/connections/services/ConnectionService.ts index f228f4fa64..26e971e393 100644 --- a/packages/core/src/modules/connections/services/ConnectionService.ts +++ b/packages/core/src/modules/connections/services/ConnectionService.ts @@ -447,8 +447,8 @@ export class ConnectionService { previousSentMessage, previousReceivedMessage, }: { - previousSentMessage?: AgentMessage - previousReceivedMessage?: AgentMessage + previousSentMessage?: AgentMessage | null + previousReceivedMessage?: AgentMessage | null } = {} ) { const { connection, message } = messageContext diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index e69fe6c6a5..06e7f6a712 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -1,23 +1,23 @@ -import type { CredentialFormatsFromProtocols, DeleteCredentialOptions } from './CredentialProtocolOptions' import type { AcceptCredentialOptions, AcceptCredentialOfferOptions, AcceptCredentialProposalOptions, AcceptCredentialRequestOptions, - CreateOfferOptions, + CreateCredentialOfferOptions, FindCredentialMessageReturn, FindCredentialOfferMessageReturn, FindCredentialProposalMessageReturn, FindCredentialRequestMessageReturn, - GetFormatDataReturn, + GetCredentialFormatDataReturn, NegotiateCredentialOfferOptions, NegotiateCredentialProposalOptions, OfferCredentialOptions, ProposeCredentialOptions, SendCredentialProblemReportOptions, - CredentialProtocolMap, + DeleteCredentialOptions, } from './CredentialsApiOptions' import type { CredentialProtocol } from './protocol/CredentialProtocol' +import type { CredentialFormatsFromProtocols } from './protocol/CredentialProtocolOptions' import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' import type { AgentMessage } from '../../agent/AgentMessage' import type { Query } from '../../storage/StorageService' @@ -63,7 +63,7 @@ export interface CredentialsApi { acceptCredential(options: AcceptCredentialOptions): Promise // out of band - createOffer(options: CreateOfferOptions): Promise<{ + createOffer(options: CreateCredentialOfferOptions): Promise<{ message: AgentMessage credentialRecord: CredentialExchangeRecord }> @@ -77,7 +77,7 @@ export interface CredentialsApi { findById(credentialRecordId: string): Promise deleteById(credentialRecordId: string, options?: DeleteCredentialOptions): Promise update(credentialRecord: CredentialExchangeRecord): Promise - getFormatData(credentialRecordId: string): Promise>> + getFormatData(credentialRecordId: string): Promise>> // DidComm Message Records findProposalMessage(credentialExchangeId: string): Promise> @@ -89,7 +89,7 @@ export interface CredentialsApi { @injectable() export class CredentialsApi implements CredentialsApi { /** - * Configuration for the connections module + * Configuration for the credentials module */ public readonly config: CredentialsModuleConfig @@ -100,7 +100,6 @@ export class CredentialsApi implements Credent private didCommMessageRepository: DidCommMessageRepository private routingService: RoutingService private logger: Logger - private credentialProtocolMap: CredentialProtocolMap public constructor( messageSender: MessageSender, @@ -123,58 +122,49 @@ export class CredentialsApi implements Credent this.didCommMessageRepository = didCommMessageRepository this.logger = logger this.config = config - - // Dynamically build service map. This will be extracted once services are registered dynamically - this.credentialProtocolMap = config.credentialProtocols.reduce( - (protocolMap, service) => ({ - ...protocolMap, - [service.version]: service, - }), - {} - ) as CredentialProtocolMap } - private getProtocol>(protocolVersion: PVT): CredentialProtocol { - if (!this.credentialProtocolMap[protocolVersion]) { + private getProtocol(protocolVersion: PVT): CredentialProtocol { + const credentialProtocol = this.config.credentialProtocols.find((protocol) => protocol.version === protocolVersion) + + if (!credentialProtocol) { throw new AriesFrameworkError(`No credential protocol registered for protocol version ${protocolVersion}`) } - return this.credentialProtocolMap[protocolVersion] as CredentialProtocol + return credentialProtocol } /** * Initiate a new credential exchange as holder by sending a credential proposal message - * to the connection with the specified credential options + * to the connection with the specified connection id. * * @param options configuration to use for the proposal * @returns Credential exchange record associated with the sent proposal message */ public async proposeCredential(options: ProposeCredentialOptions): Promise { - const service = this.getProtocol(options.protocolVersion) + const protocol = this.getProtocol(options.protocolVersion) - this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + // Assert + connectionRecord.assertReady() // will get back a credential record -> map to Credential Exchange Record - const { credentialRecord, message } = await service.createProposal(this.agentContext, { - connection, + const { credentialRecord, message } = await protocol.createProposal(this.agentContext, { + connectionRecord, credentialFormats: options.credentialFormats, comment: options.comment, autoAcceptCredential: options.autoAcceptCredential, }) - this.logger.debug('We have a message (sending outbound): ', message) - // send the message here const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) - this.logger.debug('In proposeCredential: Send Proposal to Issuer') await this.messageSender.sendMessage(outboundMessageContext) return credentialRecord } @@ -196,11 +186,15 @@ export class CredentialsApi implements Credent ) } - // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) + // with version we can get the protocol + const protocol = this.getProtocol(credentialRecord.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + + // Assert + connectionRecord.assertReady() // will get back a credential record -> map to Credential Exchange Record - const { message } = await service.acceptProposal(this.agentContext, { + const { message } = await protocol.acceptProposal(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -208,10 +202,9 @@ export class CredentialsApi implements Credent }) // send the message - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -237,9 +230,9 @@ export class CredentialsApi implements Credent } // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) - const { message } = await service.negotiateProposal(this.agentContext, { + const { message } = await protocol.negotiateProposal(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -265,22 +258,22 @@ export class CredentialsApi implements Credent * @returns Credential exchange record associated with the sent credential offer message */ public async offerCredential(options: OfferCredentialOptions): Promise { - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) - const service = this.getProtocol(options.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) + const protocol = this.getProtocol(options.protocolVersion) this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) - const { message, credentialRecord } = await service.createOffer(this.agentContext, { + const { message, credentialRecord } = await protocol.createOffer(this.agentContext, { credentialFormats: options.credentialFormats, autoAcceptCredential: options.autoAcceptCredential, comment: options.comment, - connection, + connectionRecord, }) this.logger.debug('Offer Message successfully created; message= ', message) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -298,16 +291,19 @@ export class CredentialsApi implements Credent public async acceptOffer(options: AcceptCredentialOfferOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) - this.logger.debug(`Got a credentialProtocol object for this version; version = ${service.version}`) - const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) + this.logger.debug(`Got a credentialProtocol object for this version; version = ${protocol.version}`) + const offerMessage = await protocol.findOfferMessage(this.agentContext, credentialRecord.id) // Use connection if present if (credentialRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const { message } = await service.acceptOffer(this.agentContext, { + // Assert + connectionRecord.assertReady() + + const { message } = await protocol.acceptOffer(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -316,7 +312,7 @@ export class CredentialsApi implements Credent const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -334,7 +330,7 @@ export class CredentialsApi implements Credent }) const recipientService = offerMessage.service - const { message } = await service.acceptOffer(this.agentContext, { + const { message } = await protocol.acceptOffer(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -375,8 +371,8 @@ export class CredentialsApi implements Credent credentialRecord.assertState(CredentialState.OfferReceived) // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) - await service.updateState(this.agentContext, credentialRecord, CredentialState.Declined) + const protocol = this.getProtocol(credentialRecord.protocolVersion) + await protocol.updateState(this.agentContext, credentialRecord, CredentialState.Declined) return credentialRecord } @@ -384,24 +380,28 @@ export class CredentialsApi implements Credent public async negotiateOffer(options: NegotiateCredentialOfferOptions): Promise { const credentialRecord = await this.getById(options.credentialRecordId) - const service = this.getProtocol(credentialRecord.protocolVersion) - const { message } = await service.negotiateOffer(this.agentContext, { - credentialFormats: options.credentialFormats, - credentialRecord, - comment: options.comment, - autoAcceptCredential: options.autoAcceptCredential, - }) - if (!credentialRecord.connectionId) { throw new AriesFrameworkError( `No connection id for credential record ${credentialRecord.id} not found. Connection-less issuance does not support negotiation` ) } - const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) + + // Assert + connectionRecord.assertReady() + + const protocol = this.getProtocol(credentialRecord.protocolVersion) + const { message } = await protocol.negotiateOffer(this.agentContext, { + credentialFormats: options.credentialFormats, + credentialRecord, + comment: options.comment, + autoAcceptCredential: options.autoAcceptCredential, + }) + const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: credentialRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -415,14 +415,14 @@ export class CredentialsApi implements Credent * @param options The credential options to use for the offer * @returns The credential record and credential offer message */ - public async createOffer(options: CreateOfferOptions): Promise<{ + public async createOffer(options: CreateCredentialOfferOptions): Promise<{ message: AgentMessage credentialRecord: CredentialExchangeRecord }> { - const service = this.getProtocol(options.protocolVersion) + const protocol = this.getProtocol(options.protocolVersion) this.logger.debug(`Got a credentialProtocol object for version ${options.protocolVersion}`) - const { message, credentialRecord } = await service.createOffer(this.agentContext, { + const { message, credentialRecord } = await protocol.createOffer(this.agentContext, { credentialFormats: options.credentialFormats, comment: options.comment, autoAcceptCredential: options.autoAcceptCredential, @@ -444,11 +444,11 @@ export class CredentialsApi implements Credent const credentialRecord = await this.getById(options.credentialRecordId) // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) this.logger.debug(`Got a credentialProtocol object for version ${credentialRecord.protocolVersion}`) - const { message } = await service.acceptRequest(this.agentContext, { + const { message } = await protocol.acceptRequest(this.agentContext, { credentialRecord, credentialFormats: options.credentialFormats, comment: options.comment, @@ -456,8 +456,8 @@ export class CredentialsApi implements Credent }) this.logger.debug('We have a credential message (sending outbound): ', message) - const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) - const offerMessage = await service.findOfferMessage(this.agentContext, credentialRecord.id) + const requestMessage = await protocol.findRequestMessage(this.agentContext, credentialRecord.id) + const offerMessage = await protocol.findOfferMessage(this.agentContext, credentialRecord.id) // Use connection if present if (credentialRecord.connectionId) { @@ -516,16 +516,16 @@ export class CredentialsApi implements Credent const credentialRecord = await this.getById(options.credentialRecordId) // with version we can get the Service - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) this.logger.debug(`Got a credentialProtocol object for version ${credentialRecord.protocolVersion}`) - const { message } = await service.acceptCredential(this.agentContext, { + const { message } = await protocol.acceptCredential(this.agentContext, { credentialRecord, }) - const requestMessage = await service.findRequestMessage(this.agentContext, credentialRecord.id) - const credentialMessage = await service.findCredentialMessage(this.agentContext, credentialRecord.id) + const requestMessage = await protocol.findRequestMessage(this.agentContext, credentialRecord.id) + const credentialMessage = await protocol.findCredentialMessage(this.agentContext, credentialRecord.id) if (credentialRecord.connectionId) { const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) @@ -578,12 +578,15 @@ export class CredentialsApi implements Credent } const connection = await this.connectionService.getById(this.agentContext, credentialRecord.connectionId) - const service = this.getProtocol(credentialRecord.protocolVersion) - const problemReportMessage = service.createProblemReport(this.agentContext, { message: options.message }) - problemReportMessage.setThread({ + const protocol = this.getProtocol(credentialRecord.protocolVersion) + const { message } = await protocol.createProblemReport(this.agentContext, { + description: options.description, + credentialRecord, + }) + message.setThread({ threadId: credentialRecord.threadId, }) - const outboundMessageContext = new OutboundMessageContext(problemReportMessage, { + const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, connection, associatedRecord: credentialRecord, @@ -595,11 +598,11 @@ export class CredentialsApi implements Credent public async getFormatData( credentialRecordId: string - ): Promise>> { + ): Promise>> { const credentialRecord = await this.getById(credentialRecordId) - const service = this.getProtocol(credentialRecord.protocolVersion) + const protocol = this.getProtocol(credentialRecord.protocolVersion) - return service.getFormatData(this.agentContext, credentialRecordId) + return protocol.getFormatData(this.agentContext, credentialRecordId) } /** @@ -650,8 +653,8 @@ export class CredentialsApi implements Credent */ public async deleteById(credentialId: string, options?: DeleteCredentialOptions) { const credentialRecord = await this.getById(credentialId) - const service = this.getProtocol(credentialRecord.protocolVersion) - return service.delete(this.agentContext, credentialRecord, options) + const protocol = this.getProtocol(credentialRecord.protocolVersion) + return protocol.delete(this.agentContext, credentialRecord, options) } /** @@ -664,33 +667,33 @@ export class CredentialsApi implements Credent } public async findProposalMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + const protocol = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findProposalMessage( + return protocol.findProposalMessage( this.agentContext, credentialExchangeId ) as FindCredentialProposalMessageReturn } public async findOfferMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + const protocol = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findOfferMessage(this.agentContext, credentialExchangeId) as FindCredentialOfferMessageReturn + return protocol.findOfferMessage(this.agentContext, credentialExchangeId) as FindCredentialOfferMessageReturn } public async findRequestMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + const protocol = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findRequestMessage( + return protocol.findRequestMessage( this.agentContext, credentialExchangeId ) as FindCredentialRequestMessageReturn } public async findCredentialMessage(credentialExchangeId: string): Promise> { - const service = await this.getServiceForCredentialExchangeId(credentialExchangeId) + const protocol = await this.getServiceForCredentialExchangeId(credentialExchangeId) - return service.findCredentialMessage(this.agentContext, credentialExchangeId) as FindCredentialMessageReturn + return protocol.findCredentialMessage(this.agentContext, credentialExchangeId) as FindCredentialMessageReturn } private async getServiceForCredentialExchangeId(credentialExchangeId: string) { diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 24fb0a86d1..57af50851f 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -1,10 +1,14 @@ -import type { CredentialFormatsFromProtocols, GetFormatDataReturn } from './CredentialProtocolOptions' import type { CredentialFormatPayload } from './formats' -import type { AutoAcceptCredential } from './models/CredentialAutoAcceptType' +import type { AutoAcceptCredential } from './models' import type { CredentialProtocol } from './protocol/CredentialProtocol' +import type { + CredentialFormatsFromProtocols, + DeleteCredentialOptions, + GetCredentialFormatDataReturn, +} from './protocol/CredentialProtocolOptions' -// re-export GetFormatDataReturn type from service, as it is also used in the module -export type { GetFormatDataReturn } +// re-export GetFormatDataReturn type from protocol, as it is also used in the api +export type { GetCredentialFormatDataReturn, DeleteCredentialOptions } export type FindCredentialProposalMessageReturn = ReturnType< CPs[number]['findProposalMessage'] @@ -25,23 +29,6 @@ export type FindCredentialMessageReturn = CPs[number]['version'] -/** - * Get the service map for usage in the credentials module. Will return a type mapping of protocol version to service. - * - * @example - * ``` - * type ProtocolMap = CredentialProtocolMap<[IndyCredentialFormatService], [V1CredentialProtocol]> - * - * // equal to - * type ProtocolMap = { - * v1: V1CredentialProtocol - * } - * ``` - */ -export type CredentialProtocolMap = { - [CP in CPs[number] as CP['version']]: CredentialProtocol -} - interface BaseOptions { autoAcceptCredential?: AutoAcceptCredential comment?: string @@ -79,17 +66,18 @@ export interface NegotiateCredentialProposalOptions extends BaseOptions { +export interface CreateCredentialOfferOptions + extends BaseOptions { protocolVersion: CredentialProtocolVersionType credentialFormats: CredentialFormatPayload, 'createOffer'> } /** - * Interface for CredentialsApi.offerCredentials. Extends CreateOfferOptions, will send an offer + * Interface for CredentialsApi.offerCredential. Extends CreateCredentialOfferOptions, will send an offer */ export interface OfferCredentialOptions extends BaseOptions, - CreateOfferOptions { + CreateCredentialOfferOptions { connectionId: string } @@ -138,5 +126,5 @@ export interface AcceptCredentialOptions { */ export interface SendCredentialProblemReportOptions { credentialRecordId: string - message: string + description: string } diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index b141f63f8a..a7d762e248 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -20,7 +20,7 @@ import { CredentialRepository } from './repository' */ export type DefaultCredentialProtocols = [V1CredentialProtocol, V2CredentialProtocol] -// CredentialModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. +// CredentialsModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. export type CredentialsModuleOptions = Optional< CredentialsModuleConfigOptions, 'credentialProtocols' @@ -38,7 +38,7 @@ export class CredentialsModule } diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts index 20d98623d3..ac1ffde0a9 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatService.ts @@ -1,23 +1,22 @@ import type { CredentialFormat } from './CredentialFormat' import type { - FormatCreateProposalOptions, - FormatCreateProposalReturn, - FormatProcessOptions, - FormatCreateOfferOptions, - FormatCreateOfferReturn, - FormatCreateRequestOptions, + CredentialFormatCreateProposalOptions, + CredentialFormatCreateProposalReturn, + CredentialFormatProcessOptions, + CredentialFormatCreateOfferOptions, + CredentialFormatCreateOfferReturn, + CredentialFormatCreateRequestOptions, CredentialFormatCreateReturn, - FormatAcceptRequestOptions, - FormatAcceptOfferOptions, - FormatAcceptProposalOptions, - FormatAutoRespondCredentialOptions, - FormatAutoRespondOfferOptions, - FormatAutoRespondProposalOptions, - FormatAutoRespondRequestOptions, - FormatProcessCredentialOptions, + CredentialFormatAcceptRequestOptions, + CredentialFormatAcceptOfferOptions, + CredentialFormatAcceptProposalOptions, + CredentialFormatAutoRespondCredentialOptions, + CredentialFormatAutoRespondOfferOptions, + CredentialFormatAutoRespondProposalOptions, + CredentialFormatAutoRespondRequestOptions, + CredentialFormatProcessCredentialOptions, } from './CredentialFormatServiceOptions' import type { AgentContext } from '../../../agent' -import type { Attachment } from '../../../decorators/attachment/Attachment' export interface CredentialFormatService { formatKey: CF['formatKey'] @@ -26,39 +25,58 @@ export interface CredentialFormatService - ): Promise - processProposal(agentContext: AgentContext, options: FormatProcessOptions): Promise - acceptProposal(agentContext: AgentContext, options: FormatAcceptProposalOptions): Promise + options: CredentialFormatCreateProposalOptions + ): Promise + processProposal(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise + acceptProposal( + agentContext: AgentContext, + options: CredentialFormatAcceptProposalOptions + ): Promise // offer methods - createOffer(agentContext: AgentContext, options: FormatCreateOfferOptions): Promise - processOffer(agentContext: AgentContext, options: FormatProcessOptions): Promise - acceptOffer(agentContext: AgentContext, options: FormatAcceptOfferOptions): Promise + createOffer( + agentContext: AgentContext, + options: CredentialFormatCreateOfferOptions + ): Promise + processOffer(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise + acceptOffer( + agentContext: AgentContext, + options: CredentialFormatAcceptOfferOptions + ): Promise // request methods createRequest( agentContext: AgentContext, - options: FormatCreateRequestOptions + options: CredentialFormatCreateRequestOptions ): Promise - processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise + processRequest(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise acceptRequest( agentContext: AgentContext, - options: FormatAcceptRequestOptions + options: CredentialFormatAcceptRequestOptions ): Promise // credential methods - processCredential(agentContext: AgentContext, options: FormatProcessCredentialOptions): Promise + processCredential(agentContext: AgentContext, options: CredentialFormatProcessCredentialOptions): Promise // auto accept methods - shouldAutoRespondToProposal(agentContext: AgentContext, options: FormatAutoRespondProposalOptions): boolean - shouldAutoRespondToOffer(agentContext: AgentContext, options: FormatAutoRespondOfferOptions): boolean - shouldAutoRespondToRequest(agentContext: AgentContext, options: FormatAutoRespondRequestOptions): boolean - shouldAutoRespondToCredential(agentContext: AgentContext, options: FormatAutoRespondCredentialOptions): boolean + shouldAutoRespondToProposal( + agentContext: AgentContext, + options: CredentialFormatAutoRespondProposalOptions + ): Promise + shouldAutoRespondToOffer( + agentContext: AgentContext, + options: CredentialFormatAutoRespondOfferOptions + ): Promise + shouldAutoRespondToRequest( + agentContext: AgentContext, + options: CredentialFormatAutoRespondRequestOptions + ): Promise + shouldAutoRespondToCredential( + agentContext: AgentContext, + options: CredentialFormatAutoRespondCredentialOptions + ): Promise deleteCredentialById(agentContext: AgentContext, credentialId: string): Promise - supportsFormat(format: string): boolean - - getFormatData(data: unknown, id: string): Attachment + supportsFormat(formatIdentifier: string): boolean } diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index 2f494da4ae..2d79961ebb 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -46,93 +46,88 @@ export interface CredentialFormatCreateReturn { } /** - * Base return type for all process methods. + * Base return type for all credential process methods. */ -export interface FormatProcessOptions { +export interface CredentialFormatProcessOptions { attachment: Attachment credentialRecord: CredentialExchangeRecord } -export interface FormatProcessCredentialOptions extends FormatProcessOptions { +export interface CredentialFormatProcessCredentialOptions extends CredentialFormatProcessOptions { requestAttachment: Attachment } -export interface FormatCreateProposalOptions { +export interface CredentialFormatCreateProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload<[CF], 'createProposal'> + attachmentId?: string } -export interface FormatAcceptProposalOptions { +export interface CredentialFormatAcceptProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload<[CF], 'acceptProposal'> - attachId?: string + attachmentId?: string proposalAttachment: Attachment } -export interface FormatCreateProposalReturn extends CredentialFormatCreateReturn { +export interface CredentialFormatCreateProposalReturn extends CredentialFormatCreateReturn { previewAttributes?: CredentialPreviewAttribute[] } -export interface FormatCreateOfferOptions { +export interface CredentialFormatCreateOfferOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload<[CF], 'createOffer'> - attachId?: string + attachmentId?: string } -export interface FormatAcceptOfferOptions { +export interface CredentialFormatAcceptOfferOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload<[CF], 'acceptOffer'> - attachId?: string + attachmentId?: string offerAttachment: Attachment } -export interface FormatCreateOfferReturn extends CredentialFormatCreateReturn { +export interface CredentialFormatCreateOfferReturn extends CredentialFormatCreateReturn { previewAttributes?: CredentialPreviewAttribute[] } -export interface FormatCreateRequestOptions { +export interface CredentialFormatCreateRequestOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload<[CF], 'createRequest'> } -export interface FormatAcceptRequestOptions { +export interface CredentialFormatAcceptRequestOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload<[CF], 'acceptRequest'> - attachId?: string + attachmentId?: string requestAttachment: Attachment offerAttachment?: Attachment } -export interface FormatAcceptCredentialOptions { - credentialRecord: CredentialExchangeRecord - attachId?: string - requestAttachment: Attachment - offerAttachment?: Attachment -} // Auto accept method interfaces -export interface FormatAutoRespondProposalOptions { +export interface CredentialFormatAutoRespondProposalOptions { credentialRecord: CredentialExchangeRecord proposalAttachment: Attachment offerAttachment: Attachment } -export interface FormatAutoRespondOfferOptions { +export interface CredentialFormatAutoRespondOfferOptions { credentialRecord: CredentialExchangeRecord proposalAttachment: Attachment offerAttachment: Attachment } -export interface FormatAutoRespondRequestOptions { +export interface CredentialFormatAutoRespondRequestOptions { credentialRecord: CredentialExchangeRecord proposalAttachment?: Attachment offerAttachment: Attachment requestAttachment: Attachment } -export interface FormatAutoRespondCredentialOptions { +export interface CredentialFormatAutoRespondCredentialOptions { credentialRecord: CredentialExchangeRecord proposalAttachment?: Attachment offerAttachment?: Attachment diff --git a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts index 90b6db58c0..bbe6f379f8 100644 --- a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts +++ b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts @@ -136,7 +136,7 @@ const mockCredentialRecord = ({ id: '', formats: [ { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, format: 'hlindy/cred-abstract@v2.0', }, ], @@ -248,7 +248,7 @@ describe('Indy CredentialFormatService', () => { ]) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'hlindy/cred-filter@v2.0', }) }) @@ -299,7 +299,7 @@ describe('Indy CredentialFormatService', () => { ]) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'hlindy/cred-abstract@v2.0', }) }) @@ -360,7 +360,7 @@ describe('Indy CredentialFormatService', () => { }, }) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'hlindy/cred-req@v2.0', }) @@ -387,7 +387,7 @@ describe('Indy CredentialFormatService', () => { credentialRecord, requestAttachment, offerAttachment, - attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_ATTACHMENT_ID, }) expect(attachment).toMatchObject({ @@ -407,7 +407,7 @@ describe('Indy CredentialFormatService', () => { }, }) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'hlindy/cred@v2.0', }) }) diff --git a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts index b94e44651b..59761f5808 100644 --- a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts +++ b/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts @@ -117,7 +117,7 @@ const mockCredentialRecord = ({ id: '', formats: [ { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, format: 'hlindy/cred-abstract@v2.0', }, ], @@ -220,7 +220,7 @@ describe('JsonLd CredentialFormatService', () => { }) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'aries/ld-proof-vc-detail@v1.0', }) }) @@ -255,7 +255,7 @@ describe('JsonLd CredentialFormatService', () => { expect(previewAttributes).toBeUndefined() expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'aries/ld-proof-vc-detail@v1.0', }) }) @@ -294,7 +294,7 @@ describe('JsonLd CredentialFormatService', () => { }, }) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'aries/ld-proof-vc-detail@v1.0', }) }) @@ -362,7 +362,7 @@ describe('JsonLd CredentialFormatService', () => { }, }) expect(format).toMatchObject({ - attachId: expect.any(String), + attachmentId: expect.any(String), format: 'aries/ld-proof-vc@1.0', }) }) @@ -552,7 +552,7 @@ describe('JsonLd CredentialFormatService', () => { }) // indirectly test areCredentialsEqual as black box rather than expose that method in the API - let areCredentialsEqual = jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { + let areCredentialsEqual = await jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { credentialRecord, proposalAttachment: message1, offerAttachment: message2, @@ -570,7 +570,7 @@ describe('JsonLd CredentialFormatService', () => { base64: JsonEncoder.toBase64(inputDoc2), }) - areCredentialsEqual = jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { + areCredentialsEqual = await jsonLdFormatService.shouldAutoRespondToProposal(agentContext, { credentialRecord, proposalAttachment: message1, offerAttachment: message2, diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index aca8aec43a..bee27e8983 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -5,20 +5,20 @@ import type { CredentialPreviewAttributeOptions } from '../../models/CredentialP import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' import type { CredentialFormatService } from '../CredentialFormatService' import type { - FormatAcceptOfferOptions, - FormatAcceptProposalOptions, - FormatAcceptRequestOptions, - FormatAutoRespondCredentialOptions, - FormatAutoRespondOfferOptions, - FormatAutoRespondProposalOptions, - FormatAutoRespondRequestOptions, - FormatCreateOfferOptions, - FormatCreateOfferReturn, - FormatCreateProposalOptions, - FormatCreateProposalReturn, + CredentialFormatAcceptOfferOptions, + CredentialFormatAcceptProposalOptions, + CredentialFormatAcceptRequestOptions, + CredentialFormatAutoRespondCredentialOptions, + CredentialFormatAutoRespondOfferOptions, + CredentialFormatAutoRespondProposalOptions, + CredentialFormatAutoRespondRequestOptions, + CredentialFormatCreateOfferOptions, + CredentialFormatCreateOfferReturn, + CredentialFormatCreateProposalOptions, + CredentialFormatCreateProposalReturn, CredentialFormatCreateReturn, - FormatProcessOptions, - FormatProcessCredentialOptions, + CredentialFormatProcessOptions, + CredentialFormatProcessCredentialOptions, } from '../CredentialFormatServiceOptions' import type * as Indy from 'indy-sdk' @@ -62,10 +62,11 @@ export class IndyCredentialFormatService implements CredentialFormatService - ): Promise { + { credentialFormats, credentialRecord, attachmentId }: CredentialFormatCreateProposalOptions + ): Promise { const format = new CredentialFormatSpec({ format: INDY_CRED_FILTER, + attachmentId, }) const indyFormat = credentialFormats.indy @@ -86,7 +87,7 @@ export class IndyCredentialFormatService implements CredentialFormatService { + public async processProposal( + agentContext: AgentContext, + { attachment }: CredentialFormatProcessOptions + ): Promise { const proposalJson = attachment.getDataAsJson() // fromJSON also validates @@ -112,12 +116,12 @@ export class IndyCredentialFormatService implements CredentialFormatService - ): Promise { + }: CredentialFormatAcceptProposalOptions + ): Promise { const indyFormat = credentialFormats?.indy const credentialProposal = JsonTransformer.fromJSON(proposalAttachment.getDataAsJson(), IndyCredPropose) @@ -137,7 +141,7 @@ export class IndyCredentialFormatService implements CredentialFormatService - ): Promise { + { credentialFormats, credentialRecord, attachmentId }: CredentialFormatCreateOfferOptions + ): Promise { const indyFormat = credentialFormats.indy if (!indyFormat) { @@ -165,7 +169,7 @@ export class IndyCredentialFormatService implements CredentialFormatService() @@ -188,7 +195,12 @@ export class IndyCredentialFormatService implements CredentialFormatService + { + credentialFormats, + credentialRecord, + attachmentId, + offerAttachment, + }: CredentialFormatAcceptOfferOptions ): Promise { const indyFormat = credentialFormats?.indy @@ -219,11 +231,11 @@ export class IndyCredentialFormatService implements CredentialFormatService { + public async processRequest(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise { // not needed for Indy } public async acceptRequest( agentContext: AgentContext, - { credentialRecord, attachId, offerAttachment, requestAttachment }: FormatAcceptRequestOptions + { + credentialRecord, + attachmentId, + offerAttachment, + requestAttachment, + }: CredentialFormatAcceptRequestOptions ): Promise { // Assert credential attributes const credentialAttributes = credentialRecord.credentialAttributes @@ -278,11 +295,11 @@ export class IndyCredentialFormatService implements CredentialFormatService { const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest) @@ -357,15 +374,15 @@ export class IndyCredentialFormatService implements CredentialFormatService this.supportsFormat(f.format)).map((f) => f.attachId) + const supportedAttachmentIds = formats.filter((f) => this.supportsFormat(f.format)).map((f) => f.attachmentId) const supportedAttachments = messageAttachments.filter((attachment) => supportedAttachmentIds.includes(attachment.id) ) @@ -379,9 +396,9 @@ export class IndyCredentialFormatService implements CredentialFormatService() const credentialRequestJson = requestAttachment.getDataAsJson() @@ -419,9 +436,9 @@ export class IndyCredentialFormatService implements CredentialFormatService() const credentialRequestJson = requestAttachment.getDataAsJson() @@ -441,23 +458,23 @@ export class IndyCredentialFormatService implements CredentialFormatService { + ): Promise { const indyIssuerService = agentContext.dependencyManager.resolve(IndyIssuerService) // if the proposal has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ - attachId: attachId, + attachmentId, format: INDY_CRED_ABSTRACT, }) @@ -475,7 +492,7 @@ export class IndyCredentialFormatService implements CredentialFormatService + schemaId: string + credentialDefinitionId: string + revocationRegistryId?: string + credentialRevocationId?: string +} + export class IndyCredentialInfo { - public constructor(options: IndyCredentialInfo) { + public constructor(options: IndyCredentialInfoOptions) { if (options) { this.referent = options.referent this.attributes = options.attributes diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index 36f88eb4db..52be8f6493 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -7,21 +7,21 @@ import type { import type { AgentContext } from '../../../../agent' import type { CredentialFormatService } from '../CredentialFormatService' import type { - FormatAcceptOfferOptions, - FormatAcceptProposalOptions, - FormatAcceptRequestOptions, - FormatAutoRespondOfferOptions, - FormatAutoRespondProposalOptions, - FormatAutoRespondRequestOptions, - FormatCreateOfferOptions, - FormatCreateOfferReturn, - FormatCreateProposalOptions, - FormatCreateProposalReturn, - FormatCreateRequestOptions, + CredentialFormatAcceptOfferOptions, + CredentialFormatAcceptProposalOptions, + CredentialFormatAcceptRequestOptions, + CredentialFormatAutoRespondOfferOptions, + CredentialFormatAutoRespondProposalOptions, + CredentialFormatAutoRespondRequestOptions, + CredentialFormatCreateOfferOptions, + CredentialFormatCreateOfferReturn, + CredentialFormatCreateProposalOptions, + CredentialFormatCreateProposalReturn, + CredentialFormatCreateRequestOptions, CredentialFormatCreateReturn, - FormatProcessCredentialOptions, - FormatProcessOptions, - FormatAutoRespondCredentialOptions, + CredentialFormatProcessCredentialOptions, + CredentialFormatProcessOptions, + CredentialFormatAutoRespondCredentialOptions, } from '../CredentialFormatServiceOptions' import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' @@ -52,8 +52,8 @@ export class JsonLdCredentialFormatService implements CredentialFormatService - ): Promise { + { credentialFormats }: CredentialFormatCreateProposalOptions + ): Promise { const format = new CredentialFormatSpec({ format: JSONLD_VC_DETAIL, }) @@ -67,7 +67,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { + public async processProposal( + agentContext: AgentContext, + { attachment }: CredentialFormatProcessOptions + ): Promise { const credProposalJson = attachment.getDataAsJson() if (!credProposalJson) { @@ -88,11 +91,11 @@ export class JsonLdCredentialFormatService implements CredentialFormatService - ): Promise { + { attachmentId, proposalAttachment }: CredentialFormatAcceptProposalOptions + ): Promise { // if the offer has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ - attachId, + attachmentId, format: JSONLD_VC_DETAIL, }) @@ -101,7 +104,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService - ): Promise { + { credentialFormats, attachmentId }: CredentialFormatCreateOfferOptions + ): Promise { // if the offer has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ - attachId, + attachmentId, format: JSONLD_VC_DETAIL, }) @@ -131,12 +134,12 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() if (!credentialOfferJson) { @@ -148,7 +151,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService + { attachmentId, offerAttachment }: CredentialFormatAcceptOfferOptions ): Promise { const credentialOffer = offerAttachment.getDataAsJson() @@ -156,11 +159,11 @@ export class JsonLdCredentialFormatService implements CredentialFormatService + { credentialFormats }: CredentialFormatCreateRequestOptions ): Promise { const jsonLdFormat = credentialFormats?.jsonld @@ -188,12 +191,15 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { + public async processRequest( + agentContext: AgentContext, + { attachment }: CredentialFormatProcessOptions + ): Promise { const requestJson = attachment.getDataAsJson() if (!requestJson) { @@ -206,7 +212,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService + { credentialFormats, attachmentId, requestAttachment }: CredentialFormatAcceptRequestOptions ): Promise { const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) @@ -222,7 +228,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService { const w3cCredentialService = agentContext.dependencyManager.resolve(W3cCredentialService) @@ -385,30 +391,30 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() const w3cCredential = JsonTransformer.fromJSON(credentialJson, W3cVerifiableCredential) @@ -432,7 +438,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService + options: CreateCredentialProposalOptions ): Promise> public abstract processProposal( messageContext: InboundMessageContext ): Promise public abstract acceptProposal( agentContext: AgentContext, - options: AcceptProposalOptions + options: AcceptCredentialProposalOptions ): Promise> public abstract negotiateProposal( agentContext: AgentContext, - options: NegotiateProposalOptions + options: NegotiateCredentialProposalOptions ): Promise> // methods for offer public abstract createOffer( agentContext: AgentContext, - options: CreateOfferOptions + options: CreateCredentialOfferOptions ): Promise> public abstract processOffer(messageContext: InboundMessageContext): Promise public abstract acceptOffer( agentContext: AgentContext, - options: AcceptOfferOptions + options: AcceptCredentialOfferOptions ): Promise> public abstract negotiateOffer( agentContext: AgentContext, - options: NegotiateOfferOptions + options: NegotiateCredentialOfferOptions ): Promise> // methods for request public abstract createRequest( agentContext: AgentContext, - options: CreateRequestOptions + options: CreateCredentialRequestOptions ): Promise> public abstract processRequest(messageContext: InboundMessageContext): Promise public abstract acceptRequest( agentContext: AgentContext, - options: AcceptRequestOptions + options: AcceptCredentialRequestOptions ): Promise> // methods for issue @@ -101,8 +101,8 @@ export abstract class BaseCredentialProtocol> public abstract findProposalMessage( agentContext: AgentContext, @@ -123,25 +123,10 @@ export abstract class BaseCredentialProtocol>> + ): Promise>> public abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void - /** - * Decline a credential offer - * @param credentialRecord The credential to be declined - */ - public async declineOffer( - agentContext: AgentContext, - credentialRecord: CredentialExchangeRecord - ): Promise { - credentialRecord.assertState(CredentialState.OfferReceived) - - await this.updateState(agentContext, credentialRecord, CredentialState.Declined) - - return credentialRecord - } - /** * Process a received credential {@link ProblemReportMessage}. * @@ -155,17 +140,17 @@ export abstract class BaseCredentialProtocol { + public findById(agentContext: AgentContext, proofRecordId: string): Promise { const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) - return credentialRepository.findById(agentContext, connectionId) + return credentialRepository.findById(agentContext, proofRecordId) } public async delete( diff --git a/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts index 77665a8236..b91939bbcf 100644 --- a/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/CredentialProtocol.ts @@ -1,3 +1,18 @@ +import type { + CreateCredentialProposalOptions, + CredentialProtocolMsgReturnType, + DeleteCredentialOptions, + AcceptCredentialProposalOptions, + NegotiateCredentialProposalOptions, + CreateCredentialOfferOptions, + NegotiateCredentialOfferOptions, + CreateCredentialRequestOptions, + AcceptCredentialOfferOptions, + AcceptCredentialRequestOptions, + AcceptCredentialOptions, + GetCredentialFormatDataReturn, + CreateCredentialProblemReportOptions, +} from './CredentialProtocolOptions' import type { AgentContext } from '../../../agent' import type { AgentMessage } from '../../../agent/AgentMessage' import type { FeatureRegistry } from '../../../agent/FeatureRegistry' @@ -5,21 +20,6 @@ import type { InboundMessageContext } from '../../../agent/models/InboundMessage import type { DependencyManager } from '../../../plugins' import type { Query } from '../../../storage/StorageService' import type { ProblemReportMessage } from '../../problem-reports' -import type { - CreateProposalOptions, - CredentialProtocolMsgReturnType, - DeleteCredentialOptions, - AcceptProposalOptions, - NegotiateProposalOptions, - CreateOfferOptions, - NegotiateOfferOptions, - CreateRequestOptions, - AcceptOfferOptions, - AcceptRequestOptions, - AcceptCredentialOptions, - GetFormatDataReturn, - CreateProblemReportOptions, -} from '../CredentialProtocolOptions' import type { CredentialFormatService, ExtractCredentialFormats } from '../formats' import type { CredentialState } from '../models/CredentialState' import type { CredentialExchangeRecord } from '../repository' @@ -30,42 +30,42 @@ export interface CredentialProtocol + options: CreateCredentialProposalOptions ): Promise> processProposal(messageContext: InboundMessageContext): Promise acceptProposal( agentContext: AgentContext, - options: AcceptProposalOptions + options: AcceptCredentialProposalOptions ): Promise> negotiateProposal( agentContext: AgentContext, - options: NegotiateProposalOptions + options: NegotiateCredentialProposalOptions ): Promise> // methods for offer createOffer( agentContext: AgentContext, - options: CreateOfferOptions + options: CreateCredentialOfferOptions ): Promise> processOffer(messageContext: InboundMessageContext): Promise acceptOffer( agentContext: AgentContext, - options: AcceptOfferOptions + options: AcceptCredentialOfferOptions ): Promise> negotiateOffer( agentContext: AgentContext, - options: NegotiateOfferOptions + options: NegotiateCredentialOfferOptions ): Promise> // methods for request createRequest( agentContext: AgentContext, - options: CreateRequestOptions + options: CreateCredentialRequestOptions ): Promise> processRequest(messageContext: InboundMessageContext): Promise acceptRequest( agentContext: AgentContext, - options: AcceptRequestOptions + options: AcceptCredentialRequestOptions ): Promise> // methods for issue @@ -79,7 +79,11 @@ export interface CredentialProtocol): Promise // methods for problem-report - createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage + createProblemReport( + agentContext: AgentContext, + options: CreateCredentialProblemReportOptions + ): Promise> + processProblemReport(messageContext: InboundMessageContext): Promise findProposalMessage(agentContext: AgentContext, credentialExchangeId: string): Promise findOfferMessage(agentContext: AgentContext, credentialExchangeId: string): Promise @@ -88,13 +92,7 @@ export interface CredentialProtocol>> - - declineOffer( - agentContext: AgentContext, - credentialRecord: CredentialExchangeRecord - ): Promise - processProblemReport(messageContext: InboundMessageContext): Promise + ): Promise>> // Repository methods updateState( @@ -102,13 +100,13 @@ export interface CredentialProtocol - getById(agentContext: AgentContext, credentialRecordId: string): Promise + getById(agentContext: AgentContext, credentialExchangeId: string): Promise getAll(agentContext: AgentContext): Promise findAllByQuery( agentContext: AgentContext, query: Query ): Promise - findById(agentContext: AgentContext, connectionId: string): Promise + findById(agentContext: AgentContext, credentialExchangeId: string): Promise delete( agentContext: AgentContext, credentialRecord: CredentialExchangeRecord, diff --git a/packages/core/src/modules/credentials/CredentialProtocolOptions.ts b/packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts similarity index 69% rename from packages/core/src/modules/credentials/CredentialProtocolOptions.ts rename to packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts index 5b934bdb0c..e68129f942 100644 --- a/packages/core/src/modules/credentials/CredentialProtocolOptions.ts +++ b/packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts @@ -1,15 +1,15 @@ +import type { CredentialProtocol } from './CredentialProtocol' +import type { AgentMessage } from '../../../agent/AgentMessage' +import type { ConnectionRecord } from '../../connections/repository/ConnectionRecord' import type { CredentialFormat, CredentialFormatPayload, CredentialFormatService, ExtractCredentialFormats, -} from './formats' -import type { CredentialPreviewAttributeOptions } from './models' -import type { AutoAcceptCredential } from './models/CredentialAutoAcceptType' -import type { CredentialProtocol } from './protocol/CredentialProtocol' -import type { CredentialExchangeRecord } from './repository/CredentialExchangeRecord' -import type { AgentMessage } from '../../agent/AgentMessage' -import type { ConnectionRecord } from '../connections/repository/ConnectionRecord' +} from '../formats' +import type { CredentialPreviewAttributeOptions } from '../models' +import type { AutoAcceptCredential } from '../models/CredentialAutoAcceptType' +import type { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' /** * Get the format data payload for a specific message from a list of CredentialFormat interfaces and a message @@ -20,7 +20,7 @@ import type { ConnectionRecord } from '../connections/repository/ConnectionRecor * @example * ``` * - * type OfferFormatData = FormatDataMessagePayload<[IndyCredentialFormat, JsonLdCredentialFormat], 'offer'> + * type OfferFormatData = FormatDataMessagePayload<[IndyCredentialFormat, JsonLdCredentialFormat], 'createOffer'> * * // equal to * type OfferFormatData = { @@ -33,7 +33,7 @@ import type { ConnectionRecord } from '../connections/repository/ConnectionRecor * } * ``` */ -export type FormatDataMessagePayload< +export type CredentialFormatDataMessagePayload< CFs extends CredentialFormat[] = CredentialFormat[], M extends keyof CredentialFormat['formatData'] = keyof CredentialFormat['formatData'] > = { @@ -80,66 +80,66 @@ export type CredentialFormatsFromProtocols = * } * ``` */ -export type GetFormatDataReturn = { +export type GetCredentialFormatDataReturn = { proposalAttributes?: CredentialPreviewAttributeOptions[] - proposal?: FormatDataMessagePayload - offer?: FormatDataMessagePayload + proposal?: CredentialFormatDataMessagePayload + offer?: CredentialFormatDataMessagePayload offerAttributes?: CredentialPreviewAttributeOptions[] - request?: FormatDataMessagePayload - credential?: FormatDataMessagePayload + request?: CredentialFormatDataMessagePayload + credential?: CredentialFormatDataMessagePayload } -export interface CreateProposalOptions { - connection: ConnectionRecord +export interface CreateCredentialProposalOptions { + connectionRecord: ConnectionRecord credentialFormats: CredentialFormatPayload, 'createProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptProposalOptions { +export interface AcceptCredentialProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload, 'acceptProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface NegotiateProposalOptions { +export interface NegotiateCredentialProposalOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload, 'createOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface CreateOfferOptions { +export interface CreateCredentialOfferOptions { // Create offer can also be used for connection-less, so connection is optional - connection?: ConnectionRecord + connectionRecord?: ConnectionRecord credentialFormats: CredentialFormatPayload, 'createOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptOfferOptions { +export interface AcceptCredentialOfferOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload, 'acceptOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface NegotiateOfferOptions { +export interface NegotiateCredentialOfferOptions { credentialRecord: CredentialExchangeRecord credentialFormats: CredentialFormatPayload, 'createProposal'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface CreateRequestOptions { - connection: ConnectionRecord +export interface CreateCredentialRequestOptions { + connectionRecord: ConnectionRecord credentialFormats: CredentialFormatPayload, 'createRequest'> autoAcceptCredential?: AutoAcceptCredential comment?: string } -export interface AcceptRequestOptions { +export interface AcceptCredentialRequestOptions { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload, 'acceptRequest'> autoAcceptCredential?: AutoAcceptCredential @@ -150,8 +150,9 @@ export interface AcceptCredentialOptions { credentialRecord: CredentialExchangeRecord } -export interface CreateProblemReportOptions { - message: string +export interface CreateCredentialProblemReportOptions { + credentialRecord: CredentialExchangeRecord + description: string } export interface CredentialProtocolMsgReturnType { diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts index 879ef75fd3..59338c7835 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts @@ -4,25 +4,25 @@ import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import type { DependencyManager } from '../../../../plugins' import type { ProblemReportMessage } from '../../../problem-reports' +import type { GetCredentialFormatDataReturn } from '../../CredentialsApiOptions' +import type { CredentialFormatService, ExtractCredentialFormats, IndyCredentialFormat } from '../../formats' +import type { CredentialProtocol } from '../CredentialProtocol' import type { AcceptCredentialOptions, - AcceptOfferOptions, - AcceptProposalOptions, - AcceptRequestOptions, - CreateOfferOptions, - CreateProblemReportOptions, - CreateProposalOptions, + AcceptCredentialOfferOptions, + AcceptCredentialProposalOptions, + AcceptCredentialRequestOptions, + CreateCredentialOfferOptions, + CreateCredentialProblemReportOptions, + CreateCredentialProposalOptions, CredentialProtocolMsgReturnType, - NegotiateOfferOptions, - NegotiateProposalOptions, -} from '../../CredentialProtocolOptions' -import type { GetFormatDataReturn } from '../../CredentialsApiOptions' -import type { CredentialFormatService, ExtractCredentialFormats, IndyCredentialFormat } from '../../formats' + NegotiateCredentialOfferOptions, + NegotiateCredentialProposalOptions, +} from '../CredentialProtocolOptions' import { Protocol } from '../../../../agent/models/features' import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' -import { injectable } from '../../../../plugins' import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' import { JsonTransformer } from '../../../../utils' import { isLinkedAttachment } from '../../../../utils/attachment' @@ -60,15 +60,19 @@ import { } from './messages' import { V1CredentialPreview } from './messages/V1CredentialPreview' +type IndyCredentialFormatServiceLike = CredentialFormatService + export interface V1CredentialProtocolConfig { // indyCredentialFormat must be a service that implements the `IndyCredentialFormat` interface, however it doesn't // have to be the IndyCredentialFormatService implementation per se. - indyCredentialFormat: CredentialFormatService + indyCredentialFormat: IndyCredentialFormatServiceLike } -@injectable() -export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialFormatService]> { - private indyCredentialFormat: CredentialFormatService +export class V1CredentialProtocol + extends BaseCredentialProtocol<[IndyCredentialFormatServiceLike]> + implements CredentialProtocol<[IndyCredentialFormatServiceLike]> +{ + private indyCredentialFormat: IndyCredentialFormatServiceLike public constructor({ indyCredentialFormat }: V1CredentialProtocolConfig) { super() @@ -77,7 +81,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm } /** - * The version of the issue credential protocol this service supports + * The version of the issue credential protocol this protocol supports */ public readonly version = 'v1' @@ -115,11 +119,11 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm public async createProposal( agentContext: AgentContext, { - connection, + connectionRecord, credentialFormats, comment, autoAcceptCredential, - }: CreateProposalOptions<[CredentialFormatService]> + }: CreateCredentialProposalOptions<[IndyCredentialFormatServiceLike]> ): Promise> { this.assertOnlyIndyFormat(credentialFormats) @@ -136,11 +140,11 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm // Create record const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection.id, + connectionId: connectionRecord.id, threadId: uuid(), state: CredentialState.ProposalSent, linkedAttachments: linkedAttachments?.map((linkedAttachment) => linkedAttachment.attachment), - autoAcceptCredential: autoAcceptCredential, + autoAcceptCredential, protocolVersion: 'v1', }) @@ -218,18 +222,18 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferSent) - const proposalCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1ProposeCredentialMessage, }) - const offerCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const previousSentMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalCredentialMessage ?? undefined, - previousSentMessage: offerCredentialMessage ?? undefined, + previousReceivedMessage, + previousSentMessage, }) await this.indyCredentialFormat.processProposal(messageContext.agentContext, { @@ -287,7 +291,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialFormats, comment, autoAcceptCredential, - }: AcceptProposalOptions<[CredentialFormatService]> + }: AcceptCredentialProposalOptions<[IndyCredentialFormatServiceLike]> ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') @@ -307,7 +311,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord.credentialAttributes = proposalMessage.credentialPreview?.attributes const { attachment, previewAttributes } = await this.indyCredentialFormat.acceptProposal(agentContext, { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, proposalAttachment: new Attachment({ @@ -349,7 +353,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection * associated with the credential record. * - * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @param options configuration for the offer see {@link NegotiateCredentialProposalOptions} * @returns Credential record associated with the credential offer and the corresponding new offer message * */ @@ -360,17 +364,17 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord, comment, autoAcceptCredential, - }: NegotiateProposalOptions<[CredentialFormatService]> + }: NegotiateCredentialProposalOptions<[IndyCredentialFormatServiceLike]> ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) - if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) + this.assertOnlyIndyFormat(credentialFormats) const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) const { attachment, previewAttributes } = await this.indyCredentialFormat.createOffer(agentContext, { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, }) @@ -416,11 +420,11 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialFormats, autoAcceptCredential, comment, - connection, - }: CreateOfferOptions<[CredentialFormatService]> + connectionRecord, + }: CreateCredentialOfferOptions<[IndyCredentialFormatServiceLike]> ): Promise> { // Assert - if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) + this.assertOnlyIndyFormat(credentialFormats) const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -431,7 +435,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm // Create record const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection?.id, + connectionId: connectionRecord?.id, threadId: uuid(), linkedAttachments: credentialFormats.indy.linkedAttachments?.map( (linkedAttachments) => linkedAttachments.attachment @@ -442,7 +446,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm }) const { attachment, previewAttributes } = await this.indyCredentialFormat.createOffer(agentContext, { - attachId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, credentialFormats, credentialRecord, }) @@ -499,11 +503,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm agentContext.config.logger.debug(`Processing credential offer with id ${offerMessage.id}`) - let credentialRecord = await this.findByThreadAndConnectionId( - messageContext.agentContext, - offerMessage.threadId, - connection?.id - ) + let credentialRecord = await this.findByThreadAndConnectionId(agentContext, offerMessage.threadId, connection?.id) const offerAttachment = offerMessage.getOfferAttachmentById(INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) if (!offerAttachment) { @@ -513,11 +513,11 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm } if (credentialRecord) { - const proposalCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const previousSentMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1ProposeCredentialMessage, }) - const offerCredentialMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: credentialRecord.id, messageClass: V1OfferCredentialMessage, }) @@ -526,8 +526,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalSent) connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: offerCredentialMessage ?? undefined, - previousSentMessage: proposalCredentialMessage ?? undefined, + previousReceivedMessage, + previousSentMessage, }) await this.indyCredentialFormat.processOffer(messageContext.agentContext, { @@ -587,7 +587,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialFormats, comment, autoAcceptCredential, - }: AcceptOfferOptions<[CredentialFormatService]> + }: AcceptCredentialOfferOptions<[IndyCredentialFormatServiceLike]> ): Promise> { // Assert credential credentialRecord.assertProtocolVersion('v1') @@ -610,7 +610,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm const { attachment } = await this.indyCredentialFormat.acceptOffer(agentContext, { credentialRecord, credentialFormats, - attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, offerAttachment, }) @@ -654,7 +654,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord, autoAcceptCredential, comment, - }: NegotiateOfferOptions<[CredentialFormatService]> + }: NegotiateCredentialOfferOptions<[IndyCredentialFormatServiceLike]> ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') @@ -670,7 +670,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm } if (!credentialFormats.indy) { - throw new AriesFrameworkError('Missing indy credential format in v1 create proposal call.') + throw new AriesFrameworkError('Missing indy credential format in v1 negotiate proposal call.') } const { linkedAttachments } = credentialFormats.indy @@ -808,11 +808,12 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialFormats, comment, autoAcceptCredential, - }: AcceptRequestOptions<[CredentialFormatService]> + }: AcceptCredentialRequestOptions<[IndyCredentialFormatServiceLike]> ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestReceived) + if (credentialFormats) this.assertOnlyIndyFormat(credentialFormats) const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -834,17 +835,17 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm ) } - const { attachment: credentialsAttach } = await this.indyCredentialFormat.acceptRequest(agentContext, { + const { attachment } = await this.indyCredentialFormat.acceptRequest(agentContext, { credentialRecord, requestAttachment, offerAttachment, - attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_ATTACHMENT_ID, credentialFormats, }) const issueMessage = new V1IssueCredentialMessage({ comment, - credentialAttachments: [credentialsAttach], + credentialAttachments: [attachment], attachments: credentialRecord.linkedAttachments, }) @@ -902,8 +903,8 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestSent) connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: offerCredentialMessage ?? undefined, - previousSentMessage: requestCredentialMessage ?? undefined, + previousReceivedMessage: offerCredentialMessage, + previousSentMessage: requestCredentialMessage, }) const issueAttachment = issueMessage.getCredentialAttachmentById(INDY_CREDENTIAL_ATTACHMENT_ID) @@ -1014,13 +1015,18 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm * @returns a {@link V1CredentialProblemReportMessage} * */ - public createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage { - return new V1CredentialProblemReportMessage({ + public async createProblemReport( + agentContext: AgentContext, + { credentialRecord, description }: CreateCredentialProblemReportOptions + ): Promise> { + const message = new V1CredentialProblemReportMessage({ description: { - en: options.message, + en: description, code: CredentialProblemReportReason.IssuanceAbandoned, }, }) + + return { message, credentialRecord } } // AUTO RESPOND METHODS @@ -1215,7 +1221,7 @@ export class V1CredentialProtocol extends BaseCredentialProtocol<[CredentialForm public async getFormatData( agentContext: AgentContext, credentialExchangeId: string - ): Promise]>>> { + ): Promise>> { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ this.findProposalMessage(agentContext, credentialExchangeId), diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts index f6ba0a6c22..d563555bd5 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts @@ -9,7 +9,6 @@ import type { CustomCredentialTags } from '../../../repository/CredentialExchang import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' @@ -22,7 +21,6 @@ import { uuid } from '../../../../../utils/uuid' import { AckStatus } from '../../../../common' import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' import { credDef, credReq } from '../../../__tests__/fixtures' import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' @@ -52,23 +50,17 @@ import { jest.mock('../../../repository/CredentialRepository') jest.mock('../../../formats/indy/IndyCredentialFormatService') jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') -jest.mock('../../../../routing/services/RoutingService') jest.mock('../../../../connections/services/ConnectionService') -jest.mock('../../../../../agent/Dispatcher') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock -const DispatcherMock = Dispatcher as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() -const routingService = new RoutingServiceMock() const indyCredentialFormatService = new IndyCredentialFormatServiceMock() -const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -235,10 +227,8 @@ describe('V1CredentialProtocol', () => { registerInstances: [ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], - [RoutingService, routingService], - [Dispatcher, dispatcher], - [ConnectionService, connectionService], [EventEmitter, eventEmitter], + [ConnectionService, connectionService], ], agentConfig, }) @@ -280,7 +270,7 @@ describe('V1CredentialProtocol', () => { attachment: requestAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, }), }) @@ -310,7 +300,7 @@ describe('V1CredentialProtocol', () => { expect(credentialRepository.update).toHaveBeenCalledTimes(1) expect(indyCredentialFormatService.acceptOffer).toHaveBeenCalledWith(agentContext, { credentialRecord, - attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, offerAttachment, credentialFormats: { indy: { @@ -337,7 +327,7 @@ describe('V1CredentialProtocol', () => { attachment: requestAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, }), }) @@ -443,7 +433,7 @@ describe('V1CredentialProtocol', () => { attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', - attachId: 'the-attach-id', + attachmentId: 'the-attach-id', }), }) @@ -473,7 +463,7 @@ describe('V1CredentialProtocol', () => { attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', - attachId: 'the-attach-id', + attachmentId: 'the-attach-id', }), }) @@ -513,7 +503,7 @@ describe('V1CredentialProtocol', () => { attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', - attachId: 'the-attach-id', + attachmentId: 'the-attach-id', }), }) @@ -536,7 +526,7 @@ describe('V1CredentialProtocol', () => { credentialRecord, requestAttachment, offerAttachment, - attachId: INDY_CREDENTIAL_ATTACHMENT_ID, + attachmentId: INDY_CREDENTIAL_ATTACHMENT_ID, }) }) }) @@ -708,7 +698,6 @@ describe('V1CredentialProtocol', () => { describe('createProblemReport', () => { const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249746' - const message = 'Indy error' let credential: CredentialExchangeRecord beforeEach(() => { @@ -719,16 +708,19 @@ describe('V1CredentialProtocol', () => { }) }) - test('returns problem report message base once get error', () => { + test('returns problem report message base once get error', async () => { // given mockFunction(credentialRepository.getById).mockReturnValue(Promise.resolve(credential)) // when - const credentialProblemReportMessage = credentialProtocol.createProblemReport(agentContext, { message }) + const { message } = await credentialProtocol.createProblemReport(agentContext, { + description: 'Indy error', + credentialRecord: credential, + }) - credentialProblemReportMessage.setThread({ threadId }) + message.setThread({ threadId }) // then - expect(credentialProblemReportMessage.toJSON()).toMatchObject({ + expect(message.toJSON()).toMatchObject({ '@id': expect.any(String), '@type': 'https://didcomm.org/issue-credential/1.0/problem-report', '~thread': { @@ -736,7 +728,7 @@ describe('V1CredentialProtocol', () => { }, description: { code: CredentialProblemReportReason.IssuanceAbandoned, - en: message, + en: 'Indy error', }, }) }) @@ -909,74 +901,4 @@ describe('V1CredentialProtocol', () => { expect(didCommMessageRepository.delete).toHaveBeenCalledTimes(3) }) }) - - describe('declineOffer', () => { - const threadId = 'fd9c5ddb-ec11-4acd-bc32-540736249754' - let credential: CredentialExchangeRecord - - beforeEach(() => { - credential = mockCredentialRecord({ - state: CredentialState.OfferReceived, - tags: { threadId }, - }) - }) - - test(`updates state to ${CredentialState.Declined}`, async () => { - // given - const repositoryUpdateSpy = jest.spyOn(credentialRepository, 'update') - - // when - await credentialProtocol.declineOffer(agentContext, credential) - - // then - const expectedCredentialState = { - state: CredentialState.Declined, - } - expect(repositoryUpdateSpy).toHaveBeenCalledTimes(1) - expect(repositoryUpdateSpy).toHaveBeenNthCalledWith( - 1, - agentContext, - expect.objectContaining(expectedCredentialState) - ) - }) - - test(`emits stateChange event from ${CredentialState.OfferReceived} to ${CredentialState.Declined}`, async () => { - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - // given - mockFunction(credentialRepository.getSingleByQuery).mockReturnValue(Promise.resolve(credential)) - - // when - await credentialProtocol.declineOffer(agentContext, credential) - - // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - type: 'CredentialStateChanged', - metadata: { - contextCorrelationId: 'mock', - }, - payload: { - previousState: CredentialState.OfferReceived, - credentialRecord: expect.objectContaining({ - state: CredentialState.Declined, - }), - }, - }) - }) - - const validState = CredentialState.OfferReceived - const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) - test(`throws an error when state transition is invalid`, async () => { - await Promise.all( - invalidCredentialStates.map(async (state) => { - await expect( - credentialProtocol.declineOffer(agentContext, mockCredentialRecord({ state, tags: { threadId } })) - ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) - }) - ) - }) - }) }) diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts index 78841df7fe..d1c27861b2 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts @@ -1,5 +1,5 @@ import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateOfferOptions, CreateProposalOptions } from '../../../CredentialProtocolOptions' +import type { CreateCredentialOfferOptions, CreateCredentialProposalOptions } from '../../CredentialProtocolOptions' import { Subject } from 'rxjs' @@ -70,7 +70,7 @@ const agentContext = getAgentContext({ // @ts-ignore indyCredentialFormatService.credentialRecordType = 'indy' -const connection = getMockConnection({ +const connectionRecord = getMockConnection({ id: '123', state: DidExchangeState.Completed, }) @@ -107,7 +107,7 @@ describe('V1CredentialProtocolProposeOffer', () => { beforeEach(async () => { // mock function implementations - mockFunction(connectionService.getById).mockResolvedValue(connection) + mockFunction(connectionService.getById).mockResolvedValue(connectionRecord) mockFunction(indyLedgerService.getCredentialDefinition).mockResolvedValue(credDef) mockFunction(indyLedgerService.getSchema).mockResolvedValue(schema) @@ -121,8 +121,8 @@ describe('V1CredentialProtocolProposeOffer', () => { }) describe('createProposal', () => { - const proposeOptions: CreateProposalOptions<[IndyCredentialFormatService]> = { - connection, + const proposeOptions: CreateCredentialProposalOptions<[IndyCredentialFormatService]> = { + connectionRecord: connectionRecord, credentialFormats: { indy: { credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', @@ -136,6 +136,7 @@ describe('V1CredentialProtocolProposeOffer', () => { }, comment: 'v1 propose credential test', } + test(`creates credential record in ${CredentialState.OfferSent} state with offer, thread id`, async () => { const repositorySaveSpy = jest.spyOn(credentialRepository, 'save') @@ -143,7 +144,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: proposalAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-proposal', + attachmentId: 'indy-proposal', }), }) @@ -157,7 +158,7 @@ describe('V1CredentialProtocolProposeOffer', () => { type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), - connectionId: connection.id, + connectionId: connectionRecord.id, state: CredentialState.ProposalSent, }) ) @@ -171,7 +172,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: proposalAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-proposal', + attachmentId: 'indy-proposal', }), }) @@ -196,7 +197,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: proposalAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-proposal', + attachmentId: 'indy-proposal', }), previewAttributes: credentialPreview.attributes, }) @@ -233,9 +234,9 @@ describe('V1CredentialProtocolProposeOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateOfferOptions<[IndyCredentialFormatService]> = { + const offerOptions: CreateCredentialOfferOptions<[IndyCredentialFormatService]> = { comment: 'some comment', - connection, + connectionRecord, credentialFormats: { indy: { attributes: credentialPreview.attributes, @@ -249,7 +250,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: offerAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-offer', + attachmentId: 'indy-offer', }), previewAttributes: credentialPreview.attributes, }) @@ -267,7 +268,7 @@ describe('V1CredentialProtocolProposeOffer', () => { id: expect.any(String), createdAt: expect.any(Date), threadId: createdCredentialRecord.threadId, - connectionId: connection.id, + connectionId: connectionRecord.id, state: CredentialState.OfferSent, }) }) @@ -280,7 +281,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: offerAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-offer', + attachmentId: 'indy-offer', }), previewAttributes: credentialPreview.attributes, }) @@ -306,7 +307,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: offerAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-offer', + attachmentId: 'indy-offer', }), }) @@ -320,7 +321,7 @@ describe('V1CredentialProtocolProposeOffer', () => { attachment: offerAttachment, format: new CredentialFormatSpec({ format: 'indy', - attachId: 'indy-offer', + attachmentId: 'indy-offer', }), previewAttributes: credentialPreview.attributes, }) @@ -356,7 +357,10 @@ describe('V1CredentialProtocolProposeOffer', () => { credentialPreview: credentialPreview, offerAttachments: [offerAttachment], }) - const messageContext = new InboundMessageContext(credentialOfferMessage, { agentContext, connection }) + const messageContext = new InboundMessageContext(credentialOfferMessage, { + agentContext, + connection: connectionRecord, + }) test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { // when @@ -371,7 +375,7 @@ describe('V1CredentialProtocolProposeOffer', () => { id: expect.any(String), createdAt: expect.any(Date), threadId: credentialOfferMessage.id, - connectionId: connection.id, + connectionId: connectionRecord.id, state: CredentialState.OfferReceived, credentialAttributes: undefined, }) diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts index 3e2f46e8c6..2772595d33 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts @@ -121,12 +121,4 @@ export class V1ProposeCredentialMessage extends AgentMessage { @IsOptional() @Matches(indyDidRegex) public issuerDid?: string - - public getAttachment(): Attachment | undefined { - if (this.appendedAttachments) { - return this.appendedAttachments[0] - } else { - return undefined - } - } } diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index 97e5489378..1def0ac9f4 100644 --- a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts @@ -568,6 +568,6 @@ export class CredentialFormatCoordinator if (!format) throw new AriesFrameworkError(`No attachment found for service ${credentialFormatService.formatKey}`) - return format.attachId + return format.attachmentId } } diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts index 7fac43d7e0..df39187210 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts @@ -5,21 +5,6 @@ import type { MessageHandlerInboundMessage } from '../../../../agent/MessageHand import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import type { DependencyManager } from '../../../../plugins' import type { ProblemReportMessage } from '../../../problem-reports' -import type { - AcceptCredentialOptions, - AcceptOfferOptions, - AcceptProposalOptions, - AcceptRequestOptions, - CreateOfferOptions, - CreateProposalOptions, - CreateRequestOptions, - CredentialProtocolMsgReturnType, - FormatDataMessagePayload, - CreateProblemReportOptions, - GetFormatDataReturn, - NegotiateOfferOptions, - NegotiateProposalOptions, -} from '../../CredentialProtocolOptions' import type { CredentialFormat, CredentialFormatPayload, @@ -27,6 +12,22 @@ import type { ExtractCredentialFormats, } from '../../formats' import type { CredentialFormatSpec } from '../../models/CredentialFormatSpec' +import type { CredentialProtocol } from '../CredentialProtocol' +import type { + AcceptCredentialOptions, + AcceptCredentialOfferOptions, + AcceptCredentialProposalOptions, + AcceptCredentialRequestOptions, + CreateCredentialOfferOptions, + CreateCredentialProposalOptions, + CreateCredentialRequestOptions, + CredentialProtocolMsgReturnType, + CredentialFormatDataMessagePayload, + CreateCredentialProblemReportOptions, + GetCredentialFormatDataReturn, + NegotiateCredentialOfferOptions, + NegotiateCredentialProposalOptions, +} from '../CredentialProtocolOptions' import { Protocol } from '../../../../agent/models/features/Protocol' import { AriesFrameworkError } from '../../../../error' @@ -64,9 +65,10 @@ export interface V2CredentialProtocolConfig extends BaseCredentialProtocol { +export class V2CredentialProtocol + extends BaseCredentialProtocol + implements CredentialProtocol +{ private credentialFormatCoordinator = new CredentialFormatCoordinator() private credentialFormats: CFs @@ -95,7 +97,7 @@ export class V2CredentialProtocol< new V2CredentialProblemReportHandler(this), ]) - // Register Issue Credential V1 in feature registry, with supported roles + // Register Issue Credential V2 in feature registry, with supported roles featureRegistry.register( new Protocol({ id: 'https://didcomm.org/issue-credential/2.0', @@ -113,7 +115,7 @@ export class V2CredentialProtocol< */ public async createProposal( agentContext: AgentContext, - { connection, credentialFormats, comment, autoAcceptCredential }: CreateProposalOptions + { connectionRecord, credentialFormats, comment, autoAcceptCredential }: CreateCredentialProposalOptions ): Promise> { agentContext.config.logger.debug('Get the Format Service and Create Proposal Message') @@ -125,7 +127,7 @@ export class V2CredentialProtocol< } const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection.id, + connectionId: connectionRecord.id, threadId: uuid(), state: CredentialState.ProposalSent, autoAcceptCredential, @@ -169,7 +171,7 @@ export class V2CredentialProtocol< connection?.id ) - const formatServices = this.getFormatServicesFromMessage(agentContext, proposalMessage.formats) + const formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to process proposal. No supported formats`) } @@ -230,7 +232,7 @@ export class V2CredentialProtocol< public async acceptProposal( agentContext: AgentContext, - { credentialRecord, credentialFormats, autoAcceptCredential, comment }: AcceptProposalOptions + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: AcceptCredentialProposalOptions ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') @@ -249,7 +251,7 @@ export class V2CredentialProtocol< messageClass: V2ProposeCredentialMessage, }) - formatServices = this.getFormatServicesFromMessage(agentContext, proposalMessage.formats) + formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) } // If the format services list is still empty, throw an error as we don't support any @@ -277,13 +279,13 @@ export class V2CredentialProtocol< * Negotiate a credential proposal as issuer (by sending a credential offer message) to the connection * associated with the credential record. * - * @param options configuration for the offer see {@link NegotiateProposalOptions} + * @param options configuration for the offer see {@link NegotiateCredentialProposalOptions} * @returns Credential exchange record associated with the credential offer * */ public async negotiateProposal( agentContext: AgentContext, - { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateProposalOptions + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateCredentialProposalOptions ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') @@ -324,7 +326,7 @@ export class V2CredentialProtocol< */ public async createOffer( agentContext: AgentContext, - { credentialFormats, autoAcceptCredential, comment, connection }: CreateOfferOptions + { credentialFormats, autoAcceptCredential, comment, connectionRecord }: CreateCredentialOfferOptions ): Promise> { const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) @@ -334,7 +336,7 @@ export class V2CredentialProtocol< } const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection?.id, + connectionId: connectionRecord?.id, threadId: uuid(), state: CredentialState.OfferSent, autoAcceptCredential, @@ -380,7 +382,7 @@ export class V2CredentialProtocol< connection?.id ) - const formatServices = this.getFormatServicesFromMessage(agentContext, offerMessage.formats) + const formatServices = this.getFormatServicesFromMessage(offerMessage.formats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to process offer. No supported formats`) } @@ -441,7 +443,7 @@ export class V2CredentialProtocol< public async acceptOffer( agentContext: AgentContext, - { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptOfferOptions + { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptCredentialOfferOptions ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -460,7 +462,7 @@ export class V2CredentialProtocol< messageClass: V2OfferCredentialMessage, }) - formatServices = this.getFormatServicesFromMessage(agentContext, offerMessage.formats) + formatServices = this.getFormatServicesFromMessage(offerMessage.formats) } // If the format services list is still empty, throw an error as we don't support any @@ -494,7 +496,7 @@ export class V2CredentialProtocol< */ public async negotiateOffer( agentContext: AgentContext, - { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateOfferOptions + { credentialRecord, credentialFormats, autoAcceptCredential, comment }: NegotiateCredentialOfferOptions ): Promise> { // Assert credentialRecord.assertProtocolVersion('v2') @@ -531,7 +533,7 @@ export class V2CredentialProtocol< */ public async createRequest( agentContext: AgentContext, - { credentialFormats, autoAcceptCredential, comment, connection }: CreateRequestOptions + { credentialFormats, autoAcceptCredential, comment, connectionRecord }: CreateCredentialRequestOptions ): Promise> { const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) @@ -541,7 +543,7 @@ export class V2CredentialProtocol< } const credentialRecord = new CredentialExchangeRecord({ - connectionId: connection.id, + connectionId: connectionRecord.id, threadId: uuid(), state: CredentialState.RequestSent, autoAcceptCredential, @@ -591,7 +593,7 @@ export class V2CredentialProtocol< connection?.id ) - const formatServices = this.getFormatServicesFromMessage(agentContext, requestMessage.formats) + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to process request. No supported formats`) } @@ -654,7 +656,7 @@ export class V2CredentialProtocol< public async acceptRequest( agentContext: AgentContext, - { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptRequestOptions + { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptCredentialRequestOptions ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -673,7 +675,7 @@ export class V2CredentialProtocol< messageClass: V2RequestCredentialMessage, }) - formatServices = this.getFormatServicesFromMessage(agentContext, requestMessage.formats) + formatServices = this.getFormatServicesFromMessage(requestMessage.formats) } // If the format services list is still empty, throw an error as we don't support any @@ -740,7 +742,7 @@ export class V2CredentialProtocol< previousSentMessage: requestMessage, }) - const formatServices = this.getFormatServicesFromMessage(agentContext, requestMessage.formats) + const formatServices = this.getFormatServicesFromMessage(credentialMessage.formats) if (formatServices.length === 0) { throw new AriesFrameworkError(`Unable to process credential. No supported formats`) } @@ -837,13 +839,20 @@ export class V2CredentialProtocol< * @returns a {@link V2CredentialProblemReportMessage} * */ - public createProblemReport(agentContext: AgentContext, options: CreateProblemReportOptions): ProblemReportMessage { - return new V2CredentialProblemReportMessage({ + public async createProblemReport( + agentContext: AgentContext, + { credentialRecord, description }: CreateCredentialProblemReportOptions + ): Promise> { + const message = new V2CredentialProblemReportMessage({ description: { - en: options.message, + en: description, code: CredentialProblemReportReason.IssuanceAbandoned, }, }) + + message.setThread({ threadId: credentialRecord.threadId }) + + return { credentialRecord, message } } // AUTO ACCEPT METHODS @@ -872,7 +881,7 @@ export class V2CredentialProtocol< // NOTE: we take the formats from the offerMessage so we always check all services that we last sent // Otherwise we'll only check the formats from the proposal, which could be different from the formats // we use. - const formatServices = this.getFormatServicesFromMessage(agentContext, offerMessage.formats) + const formatServices = this.getFormatServicesFromMessage(offerMessage.formats) for (const formatService of formatServices) { const offerAttachment = this.credentialFormatCoordinator.getAttachmentForService( @@ -887,7 +896,7 @@ export class V2CredentialProtocol< proposalMessage.proposalAttachments ) - const shouldAutoRespondToFormat = formatService.shouldAutoRespondToProposal(agentContext, { + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToProposal(agentContext, { credentialRecord, offerAttachment, proposalAttachment, @@ -926,7 +935,6 @@ export class V2CredentialProtocol< credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) - // Handle always / never cases if (autoAccept === AutoAcceptCredential.Always) return true if (autoAccept === AutoAcceptCredential.Never) return false @@ -937,7 +945,7 @@ export class V2CredentialProtocol< // NOTE: we take the formats from the proposalMessage so we always check all services that we last sent // Otherwise we'll only check the formats from the offer, which could be different from the formats // we use. - const formatServices = this.getFormatServicesFromMessage(agentContext, proposalMessage.formats) + const formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) for (const formatService of formatServices) { const offerAttachment = this.credentialFormatCoordinator.getAttachmentForService( @@ -952,7 +960,7 @@ export class V2CredentialProtocol< proposalMessage.proposalAttachments ) - const shouldAutoRespondToFormat = formatService.shouldAutoRespondToOffer(agentContext, { + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToOffer(agentContext, { credentialRecord, offerAttachment, proposalAttachment, @@ -1001,7 +1009,7 @@ export class V2CredentialProtocol< // NOTE: we take the formats from the offerMessage so we always check all services that we last sent // Otherwise we'll only check the formats from the request, which could be different from the formats // we use. - const formatServices = this.getFormatServicesFromMessage(agentContext, offerMessage.formats) + const formatServices = this.getFormatServicesFromMessage(offerMessage.formats) for (const formatService of formatServices) { const offerAttachment = this.credentialFormatCoordinator.getAttachmentForService( @@ -1024,7 +1032,7 @@ export class V2CredentialProtocol< requestMessage.requestAttachments ) - const shouldAutoRespondToFormat = formatService.shouldAutoRespondToRequest(agentContext, { + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToRequest(agentContext, { credentialRecord, offerAttachment, requestAttachment, @@ -1066,7 +1074,7 @@ export class V2CredentialProtocol< // NOTE: we take the formats from the requestMessage so we always check all services that we last sent // Otherwise we'll only check the formats from the credential, which could be different from the formats // we use. - const formatServices = this.getFormatServicesFromMessage(agentContext, requestMessage.formats) + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) for (const formatService of formatServices) { const offerAttachment = offerMessage @@ -1097,7 +1105,7 @@ export class V2CredentialProtocol< credentialMessage.credentialAttachments ) - const shouldAutoRespondToFormat = formatService.shouldAutoRespondToCredential(agentContext, { + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToCredential(agentContext, { credentialRecord, offerAttachment, credentialAttachment, @@ -1150,7 +1158,7 @@ export class V2CredentialProtocol< public async getFormatData( agentContext: AgentContext, credentialExchangeId: string - ): Promise>> { + ): Promise>> { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ this.findProposalMessage(agentContext, credentialExchangeId), @@ -1168,7 +1176,7 @@ export class V2CredentialProtocol< credential: [credentialMessage?.formats, credentialMessage?.credentialAttachments], } as const - const formatData: GetFormatDataReturn = { + const formatData: GetCredentialFormatDataReturn = { proposalAttributes: proposalMessage?.credentialPreview?.attributes, offerAttributes: offerMessage?.credentialPreview?.attributes, } @@ -1179,8 +1187,8 @@ export class V2CredentialProtocol< if (!formats || !attachments) continue // Find all format services associated with the message - const formatServices = this.getFormatServicesFromMessage(agentContext, formats) - const messageFormatData: FormatDataMessagePayload = {} + const formatServices = this.getFormatServicesFromMessage(formats) + const messageFormatData: CredentialFormatDataMessagePayload = {} // Loop through all of the format services, for each we will extract the attachment data and assign this to the object // using the unique format key (e.g. indy) @@ -1190,7 +1198,7 @@ export class V2CredentialProtocol< messageFormatData[formatService.formatKey] = attachment.getDataAsJson() } - formatData[messageKey as Exclude] = + formatData[messageKey as Exclude] = messageFormatData } @@ -1202,10 +1210,7 @@ export class V2CredentialProtocol< * @param messageFormats the format objects containing the format name (eg indy) * @return the credential format service objects in an array - derived from format object keys */ - private getFormatServicesFromMessage( - agentContext: AgentContext, - messageFormats: CredentialFormatSpec[] - ): CredentialFormatService[] { + private getFormatServicesFromMessage(messageFormats: CredentialFormatSpec[]): CredentialFormatService[] { const formatServices = new Set() for (const msg of messageFormats) { diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts index 5939cb70a5..8cddb48b7d 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts @@ -9,7 +9,6 @@ import { Subject } from 'rxjs' import { AriesFrameworkError, CredentialFormatSpec } from '../../../../..' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../agent/EventEmitter' import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' @@ -19,7 +18,6 @@ import { JsonEncoder } from '../../../../../utils/JsonEncoder' import { AckStatus } from '../../../../common/messages/AckMessage' import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' import { credReq } from '../../../__tests__/fixtures' import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' @@ -53,16 +51,12 @@ const CredentialRepositoryMock = CredentialRepository as jest.Mock const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock -const DispatcherMock = Dispatcher as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() -const routingService = new RoutingServiceMock() const indyCredentialFormatService = new IndyCredentialFormatServiceMock() const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() -const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -83,8 +77,6 @@ const agentContext = getAgentContext({ registerInstances: [ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], - [RoutingService, routingService], - [Dispatcher, dispatcher], [ConnectionService, connectionService], [EventEmitter, eventEmitter], ], @@ -129,7 +121,7 @@ const credentialAttachment = new Attachment({ }) const requestFormat = new CredentialFormatSpec({ - attachId: 'request-attachment-id', + attachmentId: 'request-attachment-id', format: 'hlindy/cred-filter@v2.0', }) @@ -143,17 +135,17 @@ const proposalAttachment = new Attachment({ }) const offerFormat = new CredentialFormatSpec({ - attachId: 'offer-attachment-id', + attachmentId: 'offer-attachment-id', format: 'hlindy/cred-abstract@v2.0', }) const proposalFormat = new CredentialFormatSpec({ - attachId: 'proposal-attachment-id', + attachmentId: 'proposal-attachment-id', format: 'hlindy/cred-abstract@v2.0', }) const credentialFormat = new CredentialFormatSpec({ - attachId: 'credential-attachment-id', + attachmentId: 'credential-attachment-id', format: 'hlindy/cred@v2.0', }) @@ -687,22 +679,25 @@ describe('credentialProtocol', () => { }) describe('createProblemReport', () => { - test('returns problem report message base once get error', () => { + test('returns problem report message base once get error', async () => { // given const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferReceived, threadId: 'somethreadid', connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - const message = 'Indy error' + const description = 'Indy error' mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) // when - const credentialProblemReportMessage = credentialProtocol.createProblemReport(agentContext, { message }) + const { message } = await credentialProtocol.createProblemReport(agentContext, { + description, + credentialRecord, + }) - credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) + message.setThread({ threadId: 'somethreadid' }) // then - expect(credentialProblemReportMessage.toJSON()).toMatchObject({ + expect(message.toJSON()).toMatchObject({ '@id': expect.any(String), '@type': 'https://didcomm.org/issue-credential/2.0/problem-report', '~thread': { @@ -710,21 +705,21 @@ describe('credentialProtocol', () => { }, description: { code: CredentialProblemReportReason.IssuanceAbandoned, - en: message, + en: description, }, }) }) }) describe('processProblemReport', () => { - const credentialProblemReportMessage = new V2CredentialProblemReportMessage({ + const message = new V2CredentialProblemReportMessage({ description: { en: 'Indy error', code: CredentialProblemReportReason.IssuanceAbandoned, }, }) - credentialProblemReportMessage.setThread({ threadId: 'somethreadid' }) - const messageContext = new InboundMessageContext(credentialProblemReportMessage, { + message.setThread({ threadId: 'somethreadid' }) + const messageContext = new InboundMessageContext(message, { connection, agentContext, }) @@ -875,68 +870,4 @@ describe('credentialProtocol', () => { expect(didCommMessageRepository.delete).toHaveBeenCalledTimes(3) }) }) - - describe('declineOffer', () => { - test(`updates state to ${CredentialState.Declined}`, async () => { - const credentialRecord = mockCredentialRecord({ - state: CredentialState.OfferReceived, - }) - - // when - await credentialProtocol.declineOffer(agentContext, credentialRecord) - - // then - - expect(credentialRepository.update).toHaveBeenNthCalledWith( - 1, - agentContext, - expect.objectContaining({ - state: CredentialState.Declined, - }) - ) - }) - - test(`emits stateChange event from ${CredentialState.OfferReceived} to ${CredentialState.Declined}`, async () => { - const credentialRecord = mockCredentialRecord({ - state: CredentialState.OfferReceived, - }) - - const eventListenerMock = jest.fn() - eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) - - // given - mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) - - // when - await credentialProtocol.declineOffer(agentContext, credentialRecord) - - // then - expect(eventListenerMock).toHaveBeenCalledTimes(1) - const [[event]] = eventListenerMock.mock.calls - expect(event).toMatchObject({ - type: 'CredentialStateChanged', - metadata: { - contextCorrelationId: 'mock', - }, - payload: { - previousState: CredentialState.OfferReceived, - credentialRecord: expect.objectContaining({ - state: CredentialState.Declined, - }), - }, - }) - }) - - const validState = CredentialState.OfferReceived - const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) - test(`throws an error when state transition is invalid`, async () => { - await Promise.all( - invalidCredentialStates.map(async (state) => { - await expect( - credentialProtocol.declineOffer(agentContext, mockCredentialRecord({ state })) - ).rejects.toThrowError(`Credential record is in invalid state ${state}. Valid states are: ${validState}.`) - }) - ) - }) - }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts index 7b76103178..5ae8e56632 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts @@ -1,5 +1,5 @@ import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateOfferOptions } from '../../../CredentialProtocolOptions' +import type { CreateCredentialOfferOptions } from '../../CredentialProtocolOptions' import { Subject } from 'rxjs' @@ -78,7 +78,7 @@ const agentContext = getAgentContext({ agentConfig, }) -const connection = getMockConnection({ +const connectionRecord = getMockConnection({ id: '123', state: DidExchangeState.Completed, }) @@ -88,7 +88,7 @@ const credentialPreview = V1CredentialPreview.fromRecord({ age: '99', }) const offerFormat = new CredentialFormatSpec({ - attachId: 'offer-attachment-id', + attachmentId: 'offer-attachment-id', format: 'hlindy/cred-abstract@v2.0', }) @@ -106,7 +106,7 @@ describe('V2CredentialProtocolOffer', () => { beforeEach(async () => { // mock function implementations - mockFunction(connectionService.getById).mockResolvedValue(connection) + mockFunction(connectionService.getById).mockResolvedValue(connectionRecord) mockFunction(indyLedgerService.getCredentialDefinition).mockResolvedValue(credDef) mockFunction(indyLedgerService.getSchema).mockResolvedValue(schema) @@ -120,9 +120,9 @@ describe('V2CredentialProtocolOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateOfferOptions<[IndyCredentialFormatService]> = { + const offerOptions: CreateCredentialOfferOptions<[IndyCredentialFormatService]> = { comment: 'some comment', - connection, + connectionRecord, credentialFormats: { indy: { attributes: credentialPreview.attributes, @@ -150,7 +150,7 @@ describe('V2CredentialProtocolOffer', () => { id: expect.any(String), createdAt: expect.any(Date), state: CredentialState.OfferSent, - connectionId: connection.id, + connectionId: connectionRecord.id, }) ) }) @@ -224,7 +224,10 @@ describe('V2CredentialProtocolOffer', () => { offerAttachments: [offerAttachment], }) - const messageContext = new InboundMessageContext(credentialOfferMessage, { agentContext, connection }) + const messageContext = new InboundMessageContext(credentialOfferMessage, { + agentContext, + connection: connectionRecord, + }) test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) @@ -241,7 +244,7 @@ describe('V2CredentialProtocolOffer', () => { id: expect.any(String), createdAt: expect.any(Date), threadId: credentialOfferMessage.id, - connectionId: connection.id, + connectionId: connectionRecord.id, state: CredentialState.OfferReceived, }) ) diff --git a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts index f23f53d2c0..8320314f0a 100644 --- a/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts +++ b/packages/core/src/modules/credentials/protocol/v2/handlers/V2OfferCredentialHandler.ts @@ -32,8 +32,7 @@ export class V2OfferCredentialHandler implements MessageHandler { private async acceptOffer( credentialRecord: CredentialExchangeRecord, - messageContext: MessageHandlerInboundMessage, - offerMessage?: V2OfferCredentialMessage + messageContext: MessageHandlerInboundMessage ) { messageContext.agentContext.config.logger.info(`Automatically sending request with autoAccept`) @@ -46,7 +45,7 @@ export class V2OfferCredentialHandler implements MessageHandler { connection: messageContext.connection, associatedRecord: credentialRecord, }) - } else if (offerMessage?.service) { + } else if (messageContext.message?.service) { const routingService = messageContext.agentContext.dependencyManager.resolve(RoutingService) const routing = await routingService.getRouting(messageContext.agentContext) const ourService = new ServiceDecorator({ @@ -54,7 +53,7 @@ export class V2OfferCredentialHandler implements MessageHandler { recipientKeys: [routing.recipientKey.publicKeyBase58], routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), }) - const recipientService = offerMessage.service + const recipientService = messageContext.message.service const { message } = await this.credentialProtocol.acceptOffer(messageContext.agentContext, { credentialRecord, diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts index 95bbc29663..45bfec36c0 100644 --- a/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage.ts @@ -6,7 +6,7 @@ import { Attachment } from '../../../../../decorators/attachment/Attachment' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' import { CredentialFormatSpec } from '../../../models' -export interface V2IssueCredentialMessageProps { +export interface V2IssueCredentialMessageOptions { id?: string comment?: string formats: CredentialFormatSpec[] @@ -14,7 +14,7 @@ export interface V2IssueCredentialMessageProps { } export class V2IssueCredentialMessage extends AgentMessage { - public constructor(options: V2IssueCredentialMessageProps) { + public constructor(options: V2IssueCredentialMessageOptions) { super() if (options) { diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts index b4cbbc5b01..cc7873d505 100644 --- a/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2ProposeCredentialMessage.ts @@ -8,7 +8,7 @@ import { CredentialFormatSpec } from '../../../models' import { V2CredentialPreview } from './V2CredentialPreview' -export interface V2ProposeCredentialMessageProps { +export interface V2ProposeCredentialMessageOptions { id?: string formats: CredentialFormatSpec[] proposalAttachments: Attachment[] @@ -18,21 +18,22 @@ export interface V2ProposeCredentialMessageProps { } export class V2ProposeCredentialMessage extends AgentMessage { - public constructor(props: V2ProposeCredentialMessageProps) { + public constructor(options: V2ProposeCredentialMessageOptions) { super() - if (props) { - this.id = props.id ?? this.generateId() - this.comment = props.comment - this.credentialPreview = props.credentialPreview - this.formats = props.formats - this.proposalAttachments = props.proposalAttachments - this.appendedAttachments = props.attachments + if (options) { + this.id = options.id ?? this.generateId() + this.comment = options.comment + this.credentialPreview = options.credentialPreview + this.formats = options.formats + this.proposalAttachments = options.proposalAttachments + this.appendedAttachments = options.attachments } } @Type(() => CredentialFormatSpec) - @ValidateNested() + @ValidateNested({ each: true }) @IsArray() + @IsInstance(CredentialFormatSpec, { each: true }) public formats!: CredentialFormatSpec[] @IsValidMessageType(V2ProposeCredentialMessage.type) @@ -64,6 +65,6 @@ export class V2ProposeCredentialMessage extends AgentMessage { public comment?: string public getProposalAttachmentById(id: string): Attachment | undefined { - return this.proposalAttachments.find((attachment) => attachment.id == id) + return this.proposalAttachments.find((attachment) => attachment.id === id) } } diff --git a/packages/core/src/modules/credentials/util/composeAutoAccept.ts b/packages/core/src/modules/credentials/util/composeAutoAccept.ts index 55b3e70362..ace6fdf80c 100644 --- a/packages/core/src/modules/credentials/util/composeAutoAccept.ts +++ b/packages/core/src/modules/credentials/util/composeAutoAccept.ts @@ -6,7 +6,6 @@ import { AutoAcceptCredential } from '../models/CredentialAutoAcceptType' * - Otherwise the agent config * - Otherwise {@link AutoAcceptCredential.Never} is returned */ - export function composeAutoAccept( recordConfig: AutoAcceptCredential | undefined, agentConfig: AutoAcceptCredential | undefined diff --git a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts b/packages/core/src/modules/proofs/ProofResponseCoordinator.ts deleted file mode 100644 index db625f71e0..0000000000 --- a/packages/core/src/modules/proofs/ProofResponseCoordinator.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { ProofExchangeRecord } from './repository' -import type { AgentContext } from '../../agent/context/AgentContext' - -import { injectable } from '../../plugins' - -import { ProofService } from './ProofService' -import { AutoAcceptProof } from './models/ProofAutoAcceptType' - -/** - * This class handles all the automation with all the messages in the present proof protocol - * Every function returns `true` if it should automate the flow and `false` if not - */ -@injectable() -export class ProofResponseCoordinator { - private proofService: ProofService - - public constructor(proofService: ProofService) { - this.proofService = proofService - } - - /** - * Returns the proof auto accept config based on priority: - * - The record config takes first priority - * - Otherwise the agent config - * - Otherwise {@link AutoAcceptProof.Never} is returned - */ - private static composeAutoAccept( - recordConfig: AutoAcceptProof | undefined, - agentConfig: AutoAcceptProof | undefined - ) { - return recordConfig ?? agentConfig ?? AutoAcceptProof.Never - } - - /** - * Checks whether it should automatically respond to a proposal - */ - public async shouldAutoRespondToProposal(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { - const autoAccept = ProofResponseCoordinator.composeAutoAccept( - proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs - ) - - if (autoAccept === AutoAcceptProof.Always) { - return true - } - - if (autoAccept === AutoAcceptProof.ContentApproved) { - return this.proofService.shouldAutoRespondToProposal(agentContext, proofRecord) - } - - return false - } - - /** - * Checks whether it should automatically respond to a request - */ - public async shouldAutoRespondToRequest(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { - const autoAccept = ProofResponseCoordinator.composeAutoAccept( - proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs - ) - - if (autoAccept === AutoAcceptProof.Always) { - return true - } - - if (autoAccept === AutoAcceptProof.ContentApproved) { - return this.proofService.shouldAutoRespondToRequest(agentContext, proofRecord) - } - - return false - } - - /** - * Checks whether it should automatically respond to a presentation of proof - */ - public async shouldAutoRespondToPresentation(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { - const autoAccept = ProofResponseCoordinator.composeAutoAccept( - proofRecord.autoAcceptProof, - agentContext.config.autoAcceptProofs - ) - - if (autoAccept === AutoAcceptProof.Always) { - return true - } - - if (autoAccept === AutoAcceptProof.ContentApproved) { - return this.proofService.shouldAutoRespondToPresentation(agentContext, proofRecord) - } - - return false - } -} diff --git a/packages/core/src/modules/proofs/ProofService.ts b/packages/core/src/modules/proofs/ProofService.ts deleted file mode 100644 index 2fcbe509b1..0000000000 --- a/packages/core/src/modules/proofs/ProofService.ts +++ /dev/null @@ -1,261 +0,0 @@ -import type { ProofStateChangedEvent } from './ProofEvents' -import type { ProofResponseCoordinator } from './ProofResponseCoordinator' -import type { ProofFormat } from './formats/ProofFormat' -import type { CreateProblemReportOptions } from './formats/models/ProofFormatServiceOptions' -import type { - CreateAckOptions, - CreatePresentationOptions, - CreateProofRequestFromProposalOptions, - CreateProposalAsResponseOptions, - CreateProposalOptions, - CreateRequestAsResponseOptions, - CreateRequestOptions, - DeleteProofOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, - GetFormatDataReturn, - GetRequestedCredentialsForProofRequestOptions, - ProofRequestFromProposalOptions, -} from './models/ProofServiceOptions' -import type { ProofState } from './models/ProofState' -import type { ProofExchangeRecord, ProofRepository } from './repository' -import type { AgentConfig } from '../../agent/AgentConfig' -import type { AgentMessage } from '../../agent/AgentMessage' -import type { Dispatcher } from '../../agent/Dispatcher' -import type { EventEmitter } from '../../agent/EventEmitter' -import type { AgentContext } from '../../agent/context/AgentContext' -import type { InboundMessageContext } from '../../agent/models/InboundMessageContext' -import type { Logger } from '../../logger' -import type { DidCommMessageRepository, DidCommMessageRole } from '../../storage' -import type { Wallet } from '../../wallet/Wallet' -import type { ConnectionService } from '../connections/services' -import type { MediationRecipientService, RoutingService } from '../routing' - -import { JsonTransformer } from '../../utils/JsonTransformer' - -import { ProofEventTypes } from './ProofEvents' - -export abstract class ProofService { - protected proofRepository: ProofRepository - protected didCommMessageRepository: DidCommMessageRepository - protected eventEmitter: EventEmitter - protected connectionService: ConnectionService - protected wallet: Wallet - protected logger: Logger - - public constructor( - agentConfig: AgentConfig, - proofRepository: ProofRepository, - connectionService: ConnectionService, - didCommMessageRepository: DidCommMessageRepository, - wallet: Wallet, - eventEmitter: EventEmitter - ) { - this.proofRepository = proofRepository - this.connectionService = connectionService - this.didCommMessageRepository = didCommMessageRepository - this.eventEmitter = eventEmitter - this.wallet = wallet - this.logger = agentConfig.logger - } - public abstract readonly version: string - - public emitStateChangedEvent( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord, - previousState: ProofState | null - ) { - const clonedProof = JsonTransformer.clone(proofRecord) - - this.eventEmitter.emit(agentContext, { - type: ProofEventTypes.ProofStateChanged, - payload: { - proofRecord: clonedProof, - previousState: previousState, - }, - }) - } - - /** - * Update the record to a new state and emit an state changed event. Also updates the record - * in storage. - * - * @param proofRecord The proof record to update the state for - * @param newState The state to update to - * - */ - public async updateState(agentContext: AgentContext, proofRecord: ProofExchangeRecord, newState: ProofState) { - const previousState = proofRecord.state - proofRecord.state = newState - await this.proofRepository.update(agentContext, proofRecord) - - this.emitStateChangedEvent(agentContext, proofRecord, previousState) - } - - public update(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { - return this.proofRepository.update(agentContext, proofRecord) - } - - /** - * 1. Assert (connection ready, record state) - * 2. Create proposal message - * 3. loop through all formats from ProposeProofOptions and call format service - * 4. Create and store proof record - * 5. Store proposal message - * 6. Return proposal message + proof record - */ - public abstract createProposal( - agentContext: AgentContext, - options: CreateProposalOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - - /** - * Create a proposal message in response to a received proof request message - * - * 1. assert record state - * 2. Create proposal message - * 3. loop through all formats from ProposeProofOptions and call format service - * 4. Update proof record - * 5. Create or update proposal message - * 6. Return proposal message + proof record - */ - public abstract createProposalAsResponse( - agentContext: AgentContext, - options: CreateProposalAsResponseOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - - /** - * Process a received proposal message (does not accept yet) - * - * 1. Find proof record by thread and connection id - * - * Two flows possible: - * - Proof record already exist - * 2. Assert state - * 3. Save or update proposal message in storage (didcomm message record) - * 4. Loop through all format services to process proposal message - * 5. Update & return record - * - * - Proof record does not exist yet - * 2. Create record - * 3. Save proposal message - * 4. Loop through all format services to process proposal message - * 5. Save & return record - */ - public abstract processProposal(messageContext: InboundMessageContext): Promise - - public abstract createRequest( - agentContext: AgentContext, - options: CreateRequestOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - - public abstract createRequestAsResponse( - agentContext: AgentContext, - options: CreateRequestAsResponseOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - - public abstract processRequest(messageContext: InboundMessageContext): Promise - - public abstract createPresentation( - agentContext: AgentContext, - options: CreatePresentationOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - - public abstract processPresentation(messageContext: InboundMessageContext): Promise - - public abstract createAck( - agentContext: AgentContext, - options: CreateAckOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - - public abstract processAck(messageContext: InboundMessageContext): Promise - - public abstract createProblemReport( - agentContext: AgentContext, - options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> - - public abstract processProblemReport( - messageContext: InboundMessageContext - ): Promise - - public abstract shouldAutoRespondToProposal( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise - - public abstract shouldAutoRespondToRequest( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise - - public abstract shouldAutoRespondToPresentation( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise - - public abstract registerMessageHandlers( - dispatcher: Dispatcher, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - routingService: RoutingService - ): void - - public abstract findProposalMessage(agentContext: AgentContext, proofRecordId: string): Promise - public abstract findRequestMessage(agentContext: AgentContext, proofRecordId: string): Promise - public abstract findPresentationMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise - - public async saveOrUpdatePresentationMessage( - agentContext: AgentContext, - options: { - proofRecord: ProofExchangeRecord - message: AgentMessage - role: DidCommMessageRole - } - ): Promise { - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - agentMessage: options.message, - role: options.role, - }) - } - - public async delete( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord, - options?: DeleteProofOptions - ): Promise { - await this.proofRepository.delete(agentContext, proofRecord) - - const deleteAssociatedDidCommMessages = options?.deleteAssociatedDidCommMessages ?? true - - if (deleteAssociatedDidCommMessages) { - const didCommMessages = await this.didCommMessageRepository.findByQuery(agentContext, { - associatedRecordId: proofRecord.id, - }) - for (const didCommMessage of didCommMessages) { - await this.didCommMessageRepository.delete(agentContext, didCommMessage) - } - } - } - - public abstract getRequestedCredentialsForProofRequest( - agentContext: AgentContext, - options: GetRequestedCredentialsForProofRequestOptions - ): Promise> - - public abstract autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions - ): Promise> - - public abstract createProofRequestFromProposal( - agentContext: AgentContext, - options: CreateProofRequestFromProposalOptions - ): Promise> - - public abstract getFormatData(agentContext: AgentContext, proofRecordId: string): Promise> -} diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 38f5e642bd..286637a66b 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -1,93 +1,77 @@ -import type { ProofService } from './ProofService' import type { - AcceptProofPresentationOptions, + AcceptProofOptions, AcceptProofProposalOptions, + AcceptProofRequestOptions, CreateProofRequestOptions, + DeleteProofOptions, FindProofPresentationMessageReturn, FindProofProposalMessageReturn, FindProofRequestMessageReturn, + GetCredentialsForProofRequestOptions, + GetCredentialsForProofRequestReturn, + GetProofFormatDataReturn, + NegotiateProofProposalOptions, + NegotiateProofRequestOptions, ProposeProofOptions, RequestProofOptions, - ProofServiceMap, - NegotiateRequestOptions, - NegotiateProposalOptions, + SelectCredentialsForProofRequestOptions, + SelectCredentialsForProofRequestReturn, + SendProofProblemReportOptions, } from './ProofsApiOptions' -import type { ProofFormat } from './formats/ProofFormat' -import type { IndyProofFormat } from './formats/indy/IndyProofFormat' -import type { - AutoSelectCredentialsForProofRequestOptions, - GetRequestedCredentialsForProofRequest, -} from './models/ModuleOptions' -import type { - CreatePresentationOptions, - CreateProposalOptions, - CreateRequestOptions, - CreateRequestAsResponseOptions, - CreateProofRequestFromProposalOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, - DeleteProofOptions, - GetFormatDataReturn, - CreateProposalAsResponseOptions, -} from './models/ProofServiceOptions' +import type { ProofProtocol } from './protocol/ProofProtocol' +import type { ProofFormatsFromProtocols } from './protocol/ProofProtocolOptions' import type { ProofExchangeRecord } from './repository/ProofExchangeRecord' import type { AgentMessage } from '../../agent/AgentMessage' import type { Query } from '../../storage/StorageService' -import { inject, injectable } from 'tsyringe' +import { injectable } from 'tsyringe' -import { AgentConfig } from '../../agent/AgentConfig' -import { Dispatcher } from '../../agent/Dispatcher' import { MessageSender } from '../../agent/MessageSender' import { AgentContext } from '../../agent/context/AgentContext' import { OutboundMessageContext } from '../../agent/models' -import { InjectionSymbols } from '../../constants' import { ServiceDecorator } from '../../decorators/service/ServiceDecorator' import { AriesFrameworkError } from '../../error' -import { Logger } from '../../logger' +import { DidCommMessageRepository } from '../../storage' import { DidCommMessageRole } from '../../storage/didcomm/DidCommMessageRole' import { ConnectionService } from '../connections/services/ConnectionService' -import { MediationRecipientService } from '../routing/services/MediationRecipientService' import { RoutingService } from '../routing/services/RoutingService' -import { ProofResponseCoordinator } from './ProofResponseCoordinator' +import { ProofsModuleConfig } from './ProofsModuleConfig' import { ProofState } from './models/ProofState' -import { V1ProofService } from './protocol/v1/V1ProofService' -import { V2ProofService } from './protocol/v2/V2ProofService' import { ProofRepository } from './repository/ProofRepository' -export interface ProofsApi[]> { +export interface ProofsApi { // Proposal methods - proposeProof(options: ProposeProofOptions): Promise - acceptProposal(options: AcceptProofProposalOptions): Promise - negotiateProposal(options: NegotiateProposalOptions): Promise + proposeProof(options: ProposeProofOptions): Promise + acceptProposal(options: AcceptProofProposalOptions): Promise + negotiateProposal(options: NegotiateProofProposalOptions): Promise // Request methods - requestProof(options: RequestProofOptions): Promise - acceptRequest(options: AcceptProofPresentationOptions): Promise + requestProof(options: RequestProofOptions): Promise + acceptRequest(options: AcceptProofRequestOptions): Promise declineRequest(proofRecordId: string): Promise - negotiateRequest(options: NegotiateRequestOptions): Promise + negotiateRequest(options: NegotiateProofRequestOptions): Promise // Present - acceptPresentation(proofRecordId: string): Promise + acceptPresentation(options: AcceptProofOptions): Promise // out of band - createRequest(options: CreateProofRequestOptions): Promise<{ + createRequest(options: CreateProofRequestOptions): Promise<{ message: AgentMessage proofRecord: ProofExchangeRecord }> // Auto Select - autoSelectCredentialsForProofRequest( - options: AutoSelectCredentialsForProofRequestOptions - ): Promise> + selectCredentialsForRequest( + options: SelectCredentialsForProofRequestOptions + ): Promise> - // Get Requested Credentials - getRequestedCredentialsForProofRequest( - options: AutoSelectCredentialsForProofRequestOptions - ): Promise> + // Get credentials for request + getCredentialsForRequest( + options: GetCredentialsForProofRequestOptions + ): Promise> - sendProblemReport(proofRecordId: string, message: string): Promise + sendProblemReport(options: SendProofProblemReportOptions): Promise // Record Methods getAll(): Promise @@ -96,107 +80,87 @@ export interface ProofsApi deleteById(proofId: string, options?: DeleteProofOptions): Promise update(proofRecord: ProofExchangeRecord): Promise - getFormatData(proofRecordId: string): Promise> + getFormatData(proofRecordId: string): Promise>> // DidComm Message Records - findProposalMessage(proofRecordId: string): Promise> - findRequestMessage(proofRecordId: string): Promise> - findPresentationMessage(proofRecordId: string): Promise> + findProposalMessage(proofRecordId: string): Promise> + findRequestMessage(proofRecordId: string): Promise> + findPresentationMessage(proofRecordId: string): Promise> } @injectable() -export class ProofsApi< - PFs extends ProofFormat[] = [IndyProofFormat], - PSs extends ProofService[] = [V1ProofService, V2ProofService] -> implements ProofsApi -{ +export class ProofsApi implements ProofsApi { + /** + * Configuration for the credentials module + */ + public readonly config: ProofsModuleConfig + private connectionService: ConnectionService private messageSender: MessageSender private routingService: RoutingService private proofRepository: ProofRepository + private didCommMessageRepository: DidCommMessageRepository private agentContext: AgentContext - private agentConfig: AgentConfig - private logger: Logger - private serviceMap: ProofServiceMap public constructor( - dispatcher: Dispatcher, - mediationRecipientService: MediationRecipientService, messageSender: MessageSender, connectionService: ConnectionService, agentContext: AgentContext, - agentConfig: AgentConfig, - routingService: RoutingService, - @inject(InjectionSymbols.Logger) logger: Logger, proofRepository: ProofRepository, - v1Service: V1ProofService, - v2Service: V2ProofService + routingService: RoutingService, + didCommMessageRepository: DidCommMessageRepository, + config: ProofsModuleConfig ) { this.messageSender = messageSender this.connectionService = connectionService this.proofRepository = proofRepository this.agentContext = agentContext - this.agentConfig = agentConfig this.routingService = routingService - this.logger = logger - // Dynamically build service map. This will be extracted once services are registered dynamically - this.serviceMap = [v1Service, v2Service].reduce( - (serviceMap, service) => ({ - ...serviceMap, - [service.version]: service, - }), - {} - ) as ProofServiceMap - - this.logger.debug(`Initializing Proofs Module for agent ${this.agentContext.config.label}`) - - this.registerMessageHandlers(dispatcher, mediationRecipientService) + this.didCommMessageRepository = didCommMessageRepository + this.config = config } - public getService(protocolVersion: PVT): ProofService { - if (!this.serviceMap[protocolVersion]) { - throw new AriesFrameworkError(`No proof service registered for protocol version ${protocolVersion}`) + private getProtocol(protocolVersion: PVT): ProofProtocol { + const proofProtocol = this.config.proofProtocols.find((protocol) => protocol.version === protocolVersion) + + if (!proofProtocol) { + throw new AriesFrameworkError(`No proof protocol registered for protocol version ${protocolVersion}`) } - return this.serviceMap[protocolVersion] as ProofService + return proofProtocol } /** * Initiate a new presentation exchange as prover by sending a presentation proposal message * to the connection with the specified connection id. * - * @param options multiple properties like protocol version, connection id, proof format (indy/ presentation exchange) - * to include in the message - * @returns Proof record associated with the sent proposal message + * @param options configuration to use for the proposal + * @returns Proof exchange record associated with the sent proposal message */ - public async proposeProof(options: ProposeProofOptions): Promise { - const service = this.getService(options.protocolVersion) + public async proposeProof(options: ProposeProofOptions): Promise { + const protocol = this.getProtocol(options.protocolVersion) - const { connectionId } = options - - const connection = await this.connectionService.getById(this.agentContext, connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const proposalOptions: CreateProposalOptions = { - connectionRecord: connection, + const { message, proofRecord } = await protocol.createProposal(this.agentContext, { + connectionRecord, proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, goalCode: options.goalCode, comment: options.comment, parentThreadId: options.parentThreadId, - } - - const { message, proofRecord } = await service.createProposal(this.agentContext, proposalOptions) + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -204,53 +168,42 @@ export class ProofsApi< * Accept a presentation proposal as verifier (by sending a presentation request message) to the connection * associated with the proof record. * - * @param options multiple properties like proof record id, additional configuration for creating the request - * @returns Proof record associated with the presentation request + * @param options config object for accepting the proposal + * @returns Proof exchange record associated with the presentation request */ - public async acceptProposal(options: AcceptProofProposalOptions): Promise { - const { proofRecordId } = options - - const proofRecord = await this.getById(proofRecordId) - - const service = this.getService(proofRecord.protocolVersion) + public async acceptProposal(options: AcceptProofProposalOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) if (!proofRecord.connectionId) { throw new AriesFrameworkError( - `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support presentation proposal or negotiation.` ) } - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + // with version we can get the protocol + const protocol = this.getProtocol(proofRecord.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const proofRequestFromProposalOptions: CreateProofRequestFromProposalOptions = { + const { message } = await protocol.acceptProposal(this.agentContext, { proofRecord, - } - - const proofRequest = await service.createProofRequestFromProposal( - this.agentContext, - proofRequestFromProposalOptions - ) - - const requestOptions: CreateRequestAsResponseOptions = { - proofRecord: proofRecord, - proofFormats: proofRequest.proofFormats, + proofFormats: options.proofFormats, goalCode: options.goalCode, - willConfirm: options.willConfirm ?? true, + willConfirm: options.willConfirm, comment: options.comment, - } - - const { message } = await service.createRequestAsResponse(this.agentContext, requestOptions) + autoAcceptProof: options.autoAcceptProof, + }) + // send the message const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -262,35 +215,33 @@ export class ProofsApi< * specifying which credentials to use for the proof * @returns Proof record associated with the sent request message */ - public async negotiateProposal(options: NegotiateProposalOptions): Promise { - const { proofRecordId } = options - - const proofRecord = await this.getById(proofRecordId) - - const service = this.getService(proofRecord.protocolVersion) + public async negotiateProposal(options: NegotiateProofProposalOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) if (!proofRecord.connectionId) { throw new AriesFrameworkError( - `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support negotiation.` + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support negotiation.` ) } - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const protocol = this.getProtocol(proofRecord.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const requestOptions: CreateRequestAsResponseOptions = { + const { message } = await protocol.negotiateProposal(this.agentContext, { proofRecord, proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, comment: options.comment, - } - const { message } = await service.createRequestAsResponse(this.agentContext, requestOptions) + goalCode: options.goalCode, + willConfirm: options.willConfirm, + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -305,30 +256,30 @@ export class ProofsApi< * @param options multiple properties like connection id, protocol version, proof Formats to build the proof request * @returns Proof record associated with the sent request message */ - public async requestProof(options: RequestProofOptions): Promise { - const service = this.getService(options.protocolVersion) - - const connection = await this.connectionService.getById(this.agentContext, options.connectionId) + public async requestProof(options: RequestProofOptions): Promise { + const connectionRecord = await this.connectionService.getById(this.agentContext, options.connectionId) + const protocol = this.getProtocol(options.protocolVersion) // Assert - connection.assertReady() + connectionRecord.assertReady() - const createProofRequest: CreateRequestOptions = { - connectionRecord: connection, + const { message, proofRecord } = await protocol.createRequest(this.agentContext, { + connectionRecord, proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, parentThreadId: options.parentThreadId, comment: options.comment, - } - const { message, proofRecord } = await service.createRequest(this.agentContext, createProofRequest) + goalCode: options.goalCode, + willConfirm: options.willConfirm, + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } @@ -340,32 +291,31 @@ export class ProofsApi< * specifying which credentials to use for the proof * @returns Proof record associated with the sent presentation message */ - public async acceptRequest(options: AcceptProofPresentationOptions): Promise { - const { proofRecordId, proofFormats, comment } = options - - const record = await this.getById(proofRecordId) - - const service = this.getService(record.protocolVersion) + public async acceptRequest(options: AcceptProofRequestOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) - const presentationOptions: CreatePresentationOptions = { - proofFormats, - proofRecord: record, - comment, - } - const { message, proofRecord } = await service.createPresentation(this.agentContext, presentationOptions) + const protocol = this.getProtocol(proofRecord.protocolVersion) - const requestMessage = await service.findRequestMessage(this.agentContext, proofRecord.id) + const requestMessage = await protocol.findRequestMessage(this.agentContext, proofRecord.id) // Use connection if present if (proofRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() + + const { message } = await protocol.acceptRequest(this.agentContext, { + proofFormats: options.proofFormats, + proofRecord, + comment: options.comment, + autoAcceptProof: options.autoAcceptProof, + goalCode: options.goalCode, + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) await this.messageSender.sendMessage(outboundMessageContext) @@ -377,20 +327,27 @@ export class ProofsApi< else if (requestMessage?.service) { // Create ~service decorator const routing = await this.routingService.getRouting(this.agentContext) - message.service = new ServiceDecorator({ + const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), }) - const recipientService = requestMessage.service - // Set and save ~service decorator to record (to remember our verkey) + const { message } = await protocol.acceptRequest(this.agentContext, { + proofFormats: options.proofFormats, + proofRecord, + comment: options.comment, + autoAcceptProof: options.autoAcceptProof, + goalCode: options.goalCode, + }) - await service.saveOrUpdatePresentationMessage(this.agentContext, { - proofRecord: proofRecord, - message: message, + // Set and save ~service decorator to record (to remember our verkey) + message.service = ourService + await this.didCommMessageRepository.saveOrUpdateAgentMessage(this.agentContext, { + agentMessage: message, role: DidCommMessageRole.Sender, + associatedRecordId: proofRecord.id, }) await this.messageSender.sendMessageToService( @@ -398,7 +355,7 @@ export class ProofsApi< agentContext: this.agentContext, serviceParams: { service: recipientService.resolvedDidCommService, - senderKey: message.service.resolvedDidCommService.recipientKeys[0], + senderKey: ourService.resolvedDidCommService.recipientKeys[0], returnRoute: true, }, }) @@ -414,36 +371,12 @@ export class ProofsApi< } } - /** - * Initiate a new presentation exchange as verifier by sending an out of band presentation - * request message - * - * @param options multiple properties like protocol version, proof Formats to build the proof request - * @returns the message itself and the proof record associated with the sent request message - */ - public async createRequest(options: CreateProofRequestOptions): Promise<{ - message: AgentMessage - proofRecord: ProofExchangeRecord - }> { - const service = this.getService(options.protocolVersion) - - const createProofRequest: CreateRequestOptions = { - proofFormats: options.proofFormats, - autoAcceptProof: options.autoAcceptProof, - comment: options.comment, - parentThreadId: options.parentThreadId, - } - - return await service.createRequest(this.agentContext, createProofRequest) - } - public async declineRequest(proofRecordId: string): Promise { const proofRecord = await this.getById(proofRecordId) - const service = this.getService(proofRecord.protocolVersion) - proofRecord.assertState(ProofState.RequestReceived) - await service.updateState(this.agentContext, proofRecord, ProofState.Declined) + const protocol = this.getProtocol(proofRecord.protocolVersion) + await protocol.updateState(this.agentContext, proofRecord, ProofState.Declined) return proofRecord } @@ -456,43 +389,62 @@ export class ProofsApi< * to include in the message * @returns Proof record associated with the sent proposal message */ - public async negotiateRequest(options: NegotiateRequestOptions): Promise { - const { proofRecordId } = options - const proofRecord = await this.getById(proofRecordId) - - const service = this.getService(proofRecord.protocolVersion) + public async negotiateRequest(options: NegotiateProofRequestOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) if (!proofRecord.connectionId) { throw new AriesFrameworkError( - `No connectionId found for proof record '${proofRecord.id}'. Connection-less issuance does not support presentation proposal or negotiation.` + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support presentation proposal or negotiation.` ) } - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const proposalOptions: CreateProposalAsResponseOptions = { + const protocol = this.getProtocol(proofRecord.protocolVersion) + const { message } = await protocol.negotiateRequest(this.agentContext, { proofRecord, proofFormats: options.proofFormats, autoAcceptProof: options.autoAcceptProof, goalCode: options.goalCode, comment: options.comment, - } - - const { message } = await service.createProposalAsResponse(this.agentContext, proposalOptions) + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) + await this.messageSender.sendMessage(outboundMessageContext) return proofRecord } + /** + * Initiate a new presentation exchange as verifier by sending an out of band presentation + * request message + * + * @param options multiple properties like protocol version, proof Formats to build the proof request + * @returns the message itself and the proof record associated with the sent request message + */ + public async createRequest(options: CreateProofRequestOptions): Promise<{ + message: AgentMessage + proofRecord: ProofExchangeRecord + }> { + const protocol = this.getProtocol(options.protocolVersion) + + return await protocol.createRequest(this.agentContext, { + proofFormats: options.proofFormats, + autoAcceptProof: options.autoAcceptProof, + comment: options.comment, + parentThreadId: options.parentThreadId, + goalCode: options.goalCode, + willConfirm: options.willConfirm, + }) + } + /** * Accept a presentation as prover (by sending a presentation acknowledgement message) to the connection * associated with the proof record. @@ -501,37 +453,42 @@ export class ProofsApi< * @returns Proof record associated with the sent presentation acknowledgement message * */ - public async acceptPresentation(proofRecordId: string): Promise { - const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - - const { message, proofRecord } = await service.createAck(this.agentContext, { - proofRecord: record, - }) - - const requestMessage = await service.findRequestMessage(this.agentContext, record.id) + public async acceptPresentation(options: AcceptProofOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) + const protocol = this.getProtocol(proofRecord.protocolVersion) - const presentationMessage = await service.findPresentationMessage(this.agentContext, record.id) + const requestMessage = await protocol.findRequestMessage(this.agentContext, proofRecord.id) + const presentationMessage = await protocol.findPresentationMessage(this.agentContext, proofRecord.id) // Use connection if present if (proofRecord.connectionId) { - const connection = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() + + const { message } = await protocol.acceptPresentation(this.agentContext, { + proofRecord, + }) const outboundMessageContext = new OutboundMessageContext(message, { agentContext: this.agentContext, - connection, + connection: connectionRecord, associatedRecord: proofRecord, }) await this.messageSender.sendMessage(outboundMessageContext) + + return proofRecord } // Use ~service decorator otherwise else if (requestMessage?.service && presentationMessage?.service) { - const recipientService = presentationMessage?.service + const recipientService = presentationMessage.service const ourService = requestMessage.service + const { message } = await protocol.acceptPresentation(this.agentContext, { + proofRecord, + }) + await this.messageSender.sendMessageToService( new OutboundMessageContext(message, { agentContext: this.agentContext, @@ -542,6 +499,8 @@ export class ProofsApi< }, }) ) + + return proofRecord } // Cannot send message without credentialId or ~service decorator else { @@ -549,8 +508,6 @@ export class ProofsApi< `Cannot accept presentation without connectionId or ~service decorator on presentation message.` ) } - - return record } /** @@ -561,39 +518,34 @@ export class ProofsApi< * @param options multiple properties like proof record id and optional configuration * @returns RequestedCredentials */ - public async autoSelectCredentialsForProofRequest( - options: AutoSelectCredentialsForProofRequestOptions - ): Promise> { + public async selectCredentialsForRequest( + options: SelectCredentialsForProofRequestOptions + ): Promise> { const proofRecord = await this.getById(options.proofRecordId) - const service = this.getService(proofRecord.protocolVersion) + const protocol = this.getProtocol(proofRecord.protocolVersion) - const retrievedCredentials: FormatRetrievedCredentialOptions = - await service.getRequestedCredentialsForProofRequest(this.agentContext, { - proofRecord: proofRecord, - config: options.config, - }) - return await service.autoSelectCredentialsForProofRequest(retrievedCredentials) + return protocol.selectCredentialsForRequest(this.agentContext, { + proofFormats: options.proofFormats, + proofRecord, + }) } /** - * Create a {@link RetrievedCredentials} object. Given input proof request and presentation proposal, - * use credentials in the wallet to build indy requested credentials object for input to proof creation. - * - * If restrictions allow, self attested attributes will be used. + * Get credentials in the wallet for a received proof request. * * @param options multiple properties like proof record id and optional configuration - * @returns RetrievedCredentials object */ - public async getRequestedCredentialsForProofRequest( - options: GetRequestedCredentialsForProofRequest - ): Promise> { - const record = await this.getById(options.proofRecordId) - const service = this.getService(record.protocolVersion) - - return await service.getRequestedCredentialsForProofRequest(this.agentContext, { - proofRecord: record, - config: options.config, + public async getCredentialsForRequest( + options: GetCredentialsForProofRequestOptions + ): Promise> { + const proofRecord = await this.getById(options.proofRecordId) + + const protocol = this.getProtocol(proofRecord.protocolVersion) + + return protocol.getCredentialsForRequest(this.agentContext, { + proofRecord, + proofFormats: options.proofFormats, }) } @@ -604,37 +556,38 @@ export class ProofsApi< * @param message message to send * @returns proof record associated with the proof problem report message */ - public async sendProblemReport(proofRecordId: string, message: string): Promise { - const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - if (!record.connectionId) { - throw new AriesFrameworkError(`No connectionId found for proof record '${record.id}'.`) + public async sendProblemReport(options: SendProofProblemReportOptions): Promise { + const proofRecord = await this.getById(options.proofRecordId) + if (!proofRecord.connectionId) { + throw new AriesFrameworkError(`No connectionId found for proof record '${proofRecord.id}'.`) } - const connection = await this.connectionService.getById(this.agentContext, record.connectionId) + + const protocol = this.getProtocol(proofRecord.protocolVersion) + const connectionRecord = await this.connectionService.getById(this.agentContext, proofRecord.connectionId) // Assert - connection.assertReady() + connectionRecord.assertReady() - const { message: problemReport } = await service.createProblemReport(this.agentContext, { - proofRecord: record, - description: message, + const { message: problemReport } = await protocol.createProblemReport(this.agentContext, { + proofRecord, + description: options.description, }) const outboundMessageContext = new OutboundMessageContext(problemReport, { agentContext: this.agentContext, - connection, - associatedRecord: record, + connection: connectionRecord, + associatedRecord: proofRecord, }) - await this.messageSender.sendMessage(outboundMessageContext) - return record + await this.messageSender.sendMessage(outboundMessageContext) + return proofRecord } - public async getFormatData(proofRecordId: string): Promise> { + public async getFormatData(proofRecordId: string): Promise>> { const proofRecord = await this.getById(proofRecordId) - const service = this.getService(proofRecord.protocolVersion) + const protocol = this.getProtocol(proofRecord.protocolVersion) - return service.getFormatData(this.agentContext, proofRecordId) + return protocol.getFormatData(this.agentContext, proofRecordId) } /** @@ -685,8 +638,8 @@ export class ProofsApi< */ public async deleteById(proofId: string, options?: DeleteProofOptions) { const proofRecord = await this.getById(proofId) - const service = this.getService(proofRecord.protocolVersion) - return service.delete(this.agentContext, proofRecord, options) + const protocol = this.getProtocol(proofRecord.protocolVersion) + return protocol.delete(this.agentContext, proofRecord, options) } /** @@ -725,34 +678,21 @@ export class ProofsApi< await this.proofRepository.update(this.agentContext, proofRecord) } - public async findProposalMessage(proofRecordId: string): Promise> { + public async findProposalMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - return service.findProposalMessage(this.agentContext, proofRecordId) as FindProofProposalMessageReturn + const protocol = this.getProtocol(record.protocolVersion) + return protocol.findProposalMessage(this.agentContext, proofRecordId) as FindProofProposalMessageReturn } - public async findRequestMessage(proofRecordId: string): Promise> { + public async findRequestMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - return service.findRequestMessage(this.agentContext, proofRecordId) as FindProofRequestMessageReturn + const protocol = this.getProtocol(record.protocolVersion) + return protocol.findRequestMessage(this.agentContext, proofRecordId) as FindProofRequestMessageReturn } - public async findPresentationMessage(proofRecordId: string): Promise> { + public async findPresentationMessage(proofRecordId: string): Promise> { const record = await this.getById(proofRecordId) - const service = this.getService(record.protocolVersion) - return service.findPresentationMessage(this.agentContext, proofRecordId) as FindProofPresentationMessageReturn - } - - private registerMessageHandlers(dispatcher: Dispatcher, mediationRecipientService: MediationRecipientService) { - for (const service of Object.values(this.serviceMap)) { - const proofService = service as ProofService - proofService.registerMessageHandlers( - dispatcher, - this.agentConfig, - new ProofResponseCoordinator(proofService), - mediationRecipientService, - this.routingService - ) - } + const protocol = this.getProtocol(record.protocolVersion) + return protocol.findPresentationMessage(this.agentContext, proofRecordId) as FindProofPresentationMessageReturn } } diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts index 5d23a7b131..97bfca04eb 100644 --- a/packages/core/src/modules/proofs/ProofsApiOptions.ts +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -1,97 +1,169 @@ -import type { ProofService } from './ProofService' -import type { ProofFormat, ProofFormatPayload } from './formats/ProofFormat' +import type { ProofFormatCredentialForRequestPayload, ProofFormatPayload } from './formats' import type { AutoAcceptProof } from './models' -import type { ProofConfig } from './models/ModuleOptions' +import type { ProofProtocol } from './protocol/ProofProtocol' +import type { + DeleteProofOptions, + GetProofFormatDataReturn, + ProofFormatsFromProtocols, +} from './protocol/ProofProtocolOptions' -/** - * Get the supported protocol versions based on the provided proof services. - */ -export type ProofsProtocolVersionType = PSs[number]['version'] -export type FindProofProposalMessageReturn = ReturnType -export type FindProofRequestMessageReturn = ReturnType -export type FindProofPresentationMessageReturn = ReturnType< - PSs[number]['findPresentationMessage'] +// re-export GetFormatDataReturn type from protocol, as it is also used in the api +export type { GetProofFormatDataReturn, DeleteProofOptions } + +export type FindProofProposalMessageReturn = ReturnType +export type FindProofRequestMessageReturn = ReturnType +export type FindProofPresentationMessageReturn = ReturnType< + PPs[number]['findPresentationMessage'] > /** - * Get the service map for usage in the proofs module. Will return a type mapping of protocol version to service. - * - * @example - * ``` - * type ServiceMap = ProofServiceMap<[IndyProofFormat], [V1ProofService]> - * - * // equal to - * type ServiceMap = { - * v1: V1ProofService - * } - * ``` + * Get the supported protocol versions based on the provided proof protocols. */ -export type ProofServiceMap[]> = { - [PS in PSs[number] as PS['version']]: ProofService +export type ProofsProtocolVersionType = PPs[number]['version'] + +interface BaseOptions { + autoAcceptProof?: AutoAcceptProof + comment?: string } -export interface ProposeProofOptions< - PFs extends ProofFormat[] = ProofFormat[], - PSs extends ProofService[] = ProofService[] -> { +/** + * Interface for ProofsApi.proposeProof. Will send a proposal. + */ +export interface ProposeProofOptions extends BaseOptions { connectionId: string - protocolVersion: ProofsProtocolVersionType - proofFormats: ProofFormatPayload - comment?: string + protocolVersion: ProofsProtocolVersionType + proofFormats: ProofFormatPayload, 'createProposal'> + goalCode?: string - autoAcceptProof?: AutoAcceptProof parentThreadId?: string } -export interface NegotiateRequestOptions { +/** + * Interface for ProofsApi.acceptProposal. Will send a request + * + * proofFormats is optional because this is an accept method + */ +export interface AcceptProofProposalOptions extends BaseOptions { proofRecordId: string - proofFormats: ProofFormatPayload - comment?: string + proofFormats?: ProofFormatPayload, 'acceptProposal'> + goalCode?: string - autoAcceptProof?: AutoAcceptProof + + /** @default true */ + willConfirm?: boolean } -export interface AcceptProofPresentationOptions { +/** + * Interface for ProofsApi.negotiateProposal. Will send a request + */ +export interface NegotiateProofProposalOptions extends BaseOptions { proofRecordId: string - comment?: string - proofFormats: ProofFormatPayload + proofFormats: ProofFormatPayload, 'createRequest'> + + goalCode?: string + + /** @default true */ + willConfirm?: boolean } -export interface AcceptProofProposalOptions { - proofRecordId: string - config?: ProofConfig +/** + * Interface for ProofsApi.createRequest. Will create an out of band request + */ +export interface CreateProofRequestOptions extends BaseOptions { + protocolVersion: ProofsProtocolVersionType + proofFormats: ProofFormatPayload, 'createRequest'> + goalCode?: string + parentThreadId?: string + + /** @default true */ willConfirm?: boolean - comment?: string } -export interface RequestProofOptions< - PFs extends ProofFormat[] = ProofFormat[], - PSs extends ProofService[] = ProofService[] -> { - protocolVersion: ProofsProtocolVersionType +/** + * Interface for ProofsApi.requestCredential. Extends CreateProofRequestOptions, will send a request + */ +export interface RequestProofOptions + extends BaseOptions, + CreateProofRequestOptions { connectionId: string - proofFormats: ProofFormatPayload - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string } -export interface NegotiateProposalOptions { +/** + * Interface for ProofsApi.acceptRequest. Will send a presentation + */ +export interface AcceptProofRequestOptions extends BaseOptions { proofRecordId: string - proofFormats: ProofFormatPayload - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string + proofFormats?: ProofFormatPayload, 'acceptRequest'> + + goalCode?: string + + /** @default true */ + willConfirm?: boolean } -export interface CreateProofRequestOptions< - PFs extends ProofFormat[] = ProofFormat[], - PSs extends ProofService[] = ProofService[] -> { - protocolVersion: ProofsProtocolVersionType - proofFormats: ProofFormatPayload - comment?: string - autoAcceptProof?: AutoAcceptProof - parentThreadId?: string +/** + * Interface for ProofsApi.negotiateRequest. Will send a proposal + */ +export interface NegotiateProofRequestOptions extends BaseOptions { + proofRecordId: string + proofFormats: ProofFormatPayload, 'createProposal'> + + goalCode?: string +} + +/** + * Interface for ProofsApi.acceptPresentation. Will send an ack message + */ +export interface AcceptProofOptions { + proofRecordId: string +} + +/** + * Interface for ProofsApi.getCredentialsForRequest. Will return the credentials that match the proof request + */ +export interface GetCredentialsForProofRequestOptions { + proofRecordId: string + proofFormats?: ProofFormatCredentialForRequestPayload< + ProofFormatsFromProtocols, + 'getCredentialsForRequest', + 'input' + > +} + +export interface GetCredentialsForProofRequestReturn { + proofFormats: ProofFormatCredentialForRequestPayload< + ProofFormatsFromProtocols, + 'getCredentialsForRequest', + 'output' + > +} + +/** + * Interface for ProofsApi.selectCredentialsForRequest. Will automatically select return the first/best + * credentials that match the proof request + */ +export interface SelectCredentialsForProofRequestOptions { + proofRecordId: string + proofFormats?: ProofFormatCredentialForRequestPayload< + ProofFormatsFromProtocols, + 'getCredentialsForRequest', + 'input' + > +} + +export interface SelectCredentialsForProofRequestReturn { + proofFormats: ProofFormatCredentialForRequestPayload< + ProofFormatsFromProtocols, + 'selectCredentialsForRequest', + 'output' + > +} + +/** + * Interface for ProofsApi.sendProblemReport. Will send a problem-report message + */ +export interface SendProofProblemReportOptions { + proofRecordId: string + description: string } diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 339ecd0c41..675e7a3355 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -1,22 +1,55 @@ import type { ProofsModuleConfigOptions } from './ProofsModuleConfig' +import type { ProofProtocol } from './protocol/ProofProtocol' import type { FeatureRegistry } from '../../agent/FeatureRegistry' -import type { DependencyManager, Module } from '../../plugins' - -import { Protocol } from '../../agent/models' +import type { ApiModule, DependencyManager } from '../../plugins' +import type { Optional } from '../../utils' +import type { Constructor } from '../../utils/mixins' import { ProofsApi } from './ProofsApi' import { ProofsModuleConfig } from './ProofsModuleConfig' import { IndyProofFormatService } from './formats/indy/IndyProofFormatService' -import { V1ProofService } from './protocol/v1' -import { V2ProofService } from './protocol/v2' +import { V2ProofProtocol, V1ProofProtocol } from './protocol' import { ProofRepository } from './repository' -export class ProofsModule implements Module { - public readonly config: ProofsModuleConfig - public readonly api = ProofsApi +/** + * Default proofProtocols that will be registered if the `proofProtocols` property is not configured. + */ +export type DefaultProofProtocols = [V1ProofProtocol, V2ProofProtocol] + +// ProofsModuleOptions makes the proofProtocols property optional from the config, as it will set it when not provided. +export type ProofsModuleOptions = Optional< + ProofsModuleConfigOptions, + 'proofProtocols' +> + +export class ProofsModule implements ApiModule { + public readonly config: ProofsModuleConfig + + public readonly api: Constructor> = ProofsApi + + public constructor(config?: ProofsModuleOptions) { + this.config = new ProofsModuleConfig({ + ...config, + // NOTE: the proofProtocols defaults are set in the ProofsModule rather than the ProofsModuleConfig to + // avoid dependency cycles. + proofProtocols: config?.proofProtocols ?? this.getDefaultProofProtocols(), + }) as ProofsModuleConfig + } + + /** + * Get the default proof protocols that will be registered if the `proofProtocols` property is not configured. + */ + private getDefaultProofProtocols(): DefaultProofProtocols { + // Instantiate proof formats + const indyProofFormat = new IndyProofFormatService() + + // Instantiate proof protocols + const v1ProofProtocol = new V1ProofProtocol({ indyProofFormat }) + const v2ProofProtocol = new V2ProofProtocol({ + proofFormats: [indyProofFormat], + }) - public constructor(config?: ProofsModuleConfigOptions) { - this.config = new ProofsModuleConfig(config) + return [v1ProofProtocol, v2ProofProtocol] } /** @@ -29,22 +62,11 @@ export class ProofsModule implements Module { // Config dependencyManager.registerInstance(ProofsModuleConfig, this.config) - // Services - dependencyManager.registerSingleton(V1ProofService) - dependencyManager.registerSingleton(V2ProofService) - // Repositories dependencyManager.registerSingleton(ProofRepository) - // Proof Formats - dependencyManager.registerSingleton(IndyProofFormatService) - - // Features - featureRegistry.register( - new Protocol({ - id: 'https://didcomm.org/present-proof/1.0', - roles: ['verifier', 'prover'], - }) - ) + for (const proofProtocol of this.config.proofProtocols) { + proofProtocol.register(dependencyManager, featureRegistry) + } } } diff --git a/packages/core/src/modules/proofs/ProofsModuleConfig.ts b/packages/core/src/modules/proofs/ProofsModuleConfig.ts index e0b12449e2..3526e4eb7d 100644 --- a/packages/core/src/modules/proofs/ProofsModuleConfig.ts +++ b/packages/core/src/modules/proofs/ProofsModuleConfig.ts @@ -1,27 +1,47 @@ +import type { ProofProtocol } from './protocol/ProofProtocol' + import { AutoAcceptProof } from './models/ProofAutoAcceptType' /** * ProofsModuleConfigOptions defines the interface for the options of the ProofsModuleConfig class. * This can contain optional parameters that have default values in the config class itself. */ -export interface ProofsModuleConfigOptions { +export interface ProofsModuleConfigOptions { /** * Whether to automatically accept proof messages. Applies to all present proof protocol versions. * * @default {@link AutoAcceptProof.Never} */ autoAcceptProofs?: AutoAcceptProof + + /** + * Proof protocols to make available to the proofs module. Only one proof protocol should be registered for each proof + * protocol version. + * + * When not provided, the `V1ProofProtocol` and `V2ProofProtocol` are registered by default. + * + * @default + * ``` + * [V1ProofProtocol, V2ProofProtocol] + * ``` + */ + proofProtocols: ProofProtocols } -export class ProofsModuleConfig { - private options: ProofsModuleConfigOptions +export class ProofsModuleConfig { + private options: ProofsModuleConfigOptions - public constructor(options?: ProofsModuleConfigOptions) { - this.options = options ?? {} + public constructor(options: ProofsModuleConfigOptions) { + this.options = options } /** See {@link ProofsModuleConfigOptions.autoAcceptProofs} */ public get autoAcceptProofs() { return this.options.autoAcceptProofs ?? AutoAcceptProof.Never } + + /** See {@link CredentialsModuleConfigOptions.proofProtocols} */ + public get proofProtocols() { + return this.options.proofProtocols + } } diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts index 8af9a5b2c2..c2012ed566 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts @@ -1,33 +1,60 @@ +import type { ProofProtocol } from '../protocol/ProofProtocol' + import { FeatureRegistry } from '../../../agent/FeatureRegistry' import { DependencyManager } from '../../../plugins/DependencyManager' import { ProofsApi } from '../ProofsApi' import { ProofsModule } from '../ProofsModule' -import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' -import { V1ProofService } from '../protocol/v1/V1ProofService' -import { V2ProofService } from '../protocol/v2/V2ProofService' +import { ProofsModuleConfig } from '../ProofsModuleConfig' +import { V1ProofProtocol } from '../protocol/v1/V1ProofProtocol' +import { V2ProofProtocol } from '../protocol/v2/V2ProofProtocol' import { ProofRepository } from '../repository' jest.mock('../../../plugins/DependencyManager') -const DependencyManagerMock = DependencyManager as jest.Mock +jest.mock('../../../agent/FeatureRegistry') +const DependencyManagerMock = DependencyManager as jest.Mock const dependencyManager = new DependencyManagerMock() - -jest.mock('../../../agent/FeatureRegistry') const FeatureRegistryMock = FeatureRegistry as jest.Mock - const featureRegistry = new FeatureRegistryMock() describe('ProofsModule', () => { test('registers dependencies on the dependency manager', () => { - new ProofsModule().register(dependencyManager, featureRegistry) + const proofsModule = new ProofsModule({ + proofProtocols: [], + }) + proofsModule.register(dependencyManager, featureRegistry) expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(ProofsApi) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(4) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V1ProofService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(V2ProofService) + expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerInstance).toHaveBeenCalledWith(ProofsModuleConfig, proofsModule.config) + + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ProofRepository) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyProofFormatService) + }) + + test('registers V1ProofProtocol and V2ProofProtocol if no proofProtocols are configured', () => { + const proofsModule = new ProofsModule() + + expect(proofsModule.config.proofProtocols).toEqual([expect.any(V1ProofProtocol), expect.any(V2ProofProtocol)]) + }) + + test('calls register on the provided ProofProtocols', () => { + const registerMock = jest.fn() + const proofProtocol = { + register: registerMock, + } as unknown as ProofProtocol + + const proofsModule = new ProofsModule({ + proofProtocols: [proofProtocol], + }) + + expect(proofsModule.config.proofProtocols).toEqual([proofProtocol]) + + proofsModule.register(dependencyManager, featureRegistry) + + expect(registerMock).toHaveBeenCalledTimes(1) + expect(registerMock).toHaveBeenCalledWith(dependencyManager, featureRegistry) }) }) diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModuleConfig.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModuleConfig.test.ts new file mode 100644 index 0000000000..35920a4f48 --- /dev/null +++ b/packages/core/src/modules/proofs/__tests__/ProofsModuleConfig.test.ts @@ -0,0 +1,26 @@ +import type { ProofProtocol } from '../protocol/ProofProtocol' + +import { ProofsModuleConfig } from '../ProofsModuleConfig' +import { AutoAcceptProof } from '../models' + +describe('ProofsModuleConfig', () => { + test('sets default values', () => { + const config = new ProofsModuleConfig({ + proofProtocols: [], + }) + + expect(config.autoAcceptProofs).toBe(AutoAcceptProof.Never) + expect(config.proofProtocols).toEqual([]) + }) + + test('sets values', () => { + const proofProtocol = jest.fn() as unknown as ProofProtocol + const config = new ProofsModuleConfig({ + autoAcceptProofs: AutoAcceptProof.Always, + proofProtocols: [proofProtocol], + }) + + expect(config.autoAcceptProofs).toBe(AutoAcceptProof.Always) + expect(config.proofProtocols).toEqual([proofProtocol]) + }) +}) diff --git a/packages/core/src/modules/proofs/__tests__/fixtures.ts b/packages/core/src/modules/proofs/__tests__/fixtures.ts index 10606073b8..2045f6f0f8 100644 --- a/packages/core/src/modules/proofs/__tests__/fixtures.ts +++ b/packages/core/src/modules/proofs/__tests__/fixtures.ts @@ -15,3 +15,33 @@ export const credDef = { }, }, } + +export const TEST_INPUT_DESCRIPTORS_CITIZENSHIP = { + constraints: { + fields: [ + { + path: ['$.credentialSubject.familyName'], + purpose: 'The claim must be from one of the specified issuers', + id: '1f44d55f-f161-4938-a659-f8026467f126', + }, + { + path: ['$.credentialSubject.givenName'], + purpose: 'The claim must be from one of the specified issuers', + }, + ], + }, + schema: [ + { + uri: 'https://www.w3.org/2018/credentials#VerifiableCredential', + }, + { + uri: 'https://w3id.org/citizenship#PermanentResident', + }, + { + uri: 'https://w3id.org/citizenship/v1', + }, + ], + name: "EU Driver's License", + group: ['A'], + id: 'citizenship_input_1', +} diff --git a/packages/core/src/modules/proofs/formats/ProofFormat.ts b/packages/core/src/modules/proofs/formats/ProofFormat.ts index 18fbba278d..c573c12579 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormat.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormat.ts @@ -21,21 +21,50 @@ export type ProofFormatPayload + * + * // equal to + * type SelectedCredentialsForRequest = { + * indy: { + * // ... return value for indy selected credentials ... + * }, + * presentationExchange: { + * // ... return value for presentation exchange selected credentials ... + * } + * } + * ``` + */ +export type ProofFormatCredentialForRequestPayload< + PFs extends ProofFormat[], + M extends 'selectCredentialsForRequest' | 'getCredentialsForRequest', + IO extends 'input' | 'output' +> = { + [ProofFormat in PFs[number] as ProofFormat['formatKey']]?: ProofFormat['proofFormats'][M][IO] +} + export interface ProofFormat { - formatKey: string // e.g. 'ProofManifest', cannot be shared between different formats + formatKey: string // e.g. 'presentationExchange', cannot be shared between different formats + proofFormats: { createProposal: unknown acceptProposal: unknown createRequest: unknown acceptRequest: unknown - createPresentation: unknown - acceptPresentation: unknown - createProposalAsResponse: unknown - createOutOfBandRequest: unknown - createRequestAsResponse: unknown - createProofRequestFromProposal: unknown - requestCredentials: unknown - retrieveCredentials: unknown + + getCredentialsForRequest: { + input: unknown + output: unknown + } + selectCredentialsForRequest: { + input: unknown + output: unknown + } } formatData: { proposal: unknown diff --git a/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts b/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts deleted file mode 100644 index 35e1ce33ab..0000000000 --- a/packages/core/src/modules/proofs/formats/ProofFormatConstants.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const INDY_ATTACH_ID = 'indy' -export const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' -export const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' -export const V2_INDY_PRESENTATION = 'hlindy/proof@v2.0' diff --git a/packages/core/src/modules/proofs/formats/ProofFormatService.ts b/packages/core/src/modules/proofs/formats/ProofFormatService.ts index 931d4c886f..f48db80624 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatService.ts @@ -1,81 +1,70 @@ import type { ProofFormat } from './ProofFormat' -import type { IndyProofFormat } from './indy/IndyProofFormat' -import type { GetRequestedCredentialsFormat } from './indy/IndyProofFormatsServiceOptions' -import type { ProofAttachmentFormat } from './models/ProofAttachmentFormat' import type { - CreatePresentationFormatsOptions, - FormatCreateProofProposalOptions, - CreateRequestOptions, - FormatCreatePresentationOptions, - ProcessPresentationOptions, - ProcessProposalOptions, - ProcessRequestOptions, -} from './models/ProofFormatServiceOptions' + ProofFormatAcceptProposalOptions, + ProofFormatAcceptRequestOptions, + ProofFormatCreateProposalOptions, + FormatCreateRequestOptions, + ProofFormatProcessPresentationOptions, + ProofFormatCreateReturn, + ProofFormatProcessOptions, + ProofFormatGetCredentialsForRequestOptions, + ProofFormatGetCredentialsForRequestReturn, + ProofFormatSelectCredentialsForRequestOptions, + ProofFormatSelectCredentialsForRequestReturn, + ProofFormatAutoRespondProposalOptions, + ProofFormatAutoRespondRequestOptions, + ProofFormatAutoRespondPresentationOptions, +} from './ProofFormatServiceOptions' import type { AgentContext } from '../../../agent' -import type { AgentConfig } from '../../../agent/AgentConfig' -import type { DidCommMessageRepository } from '../../../storage' -import type { - CreateRequestAsResponseOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../models/ProofServiceOptions' -import type { ProofRequestFormats } from '../models/SharedOptions' - -/** - * This abstract class is the base class for any proof format - * specific service. - * - * @export - * @abstract - * @class ProofFormatService - */ -export abstract class ProofFormatService { - protected didCommMessageRepository: DidCommMessageRepository - protected agentConfig: AgentConfig - - public abstract readonly formatKey: PF['formatKey'] - - public constructor(didCommMessageRepository: DidCommMessageRepository, agentConfig: AgentConfig) { - this.didCommMessageRepository = didCommMessageRepository - this.agentConfig = agentConfig - } - public abstract createProposal(options: FormatCreateProofProposalOptions): Promise +export interface ProofFormatService { + formatKey: PF['formatKey'] - public abstract processProposal(options: ProcessProposalOptions): Promise - - public abstract createRequest(options: CreateRequestOptions): Promise - - public abstract processRequest(options: ProcessRequestOptions): Promise - - public abstract createPresentation( + // proposal methods + createProposal( agentContext: AgentContext, - options: FormatCreatePresentationOptions - ): Promise - - public abstract processPresentation(agentContext: AgentContext, options: ProcessPresentationOptions): Promise - - public abstract createProofRequestFromProposal( - options: CreatePresentationFormatsOptions - ): Promise + options: ProofFormatCreateProposalOptions + ): Promise + processProposal(agentContext: AgentContext, options: ProofFormatProcessOptions): Promise + acceptProposal( + agentContext: AgentContext, + options: ProofFormatAcceptProposalOptions + ): Promise - public abstract getRequestedCredentialsForProofRequest( + // request methods + createRequest(agentContext: AgentContext, options: FormatCreateRequestOptions): Promise + processRequest(agentContext: AgentContext, options: ProofFormatProcessOptions): Promise + acceptRequest( agentContext: AgentContext, - options: GetRequestedCredentialsFormat - ): Promise> + options: ProofFormatAcceptRequestOptions + ): Promise - public abstract autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions<[PF]> - ): Promise> + // presentation methods + processPresentation(agentContext: AgentContext, options: ProofFormatProcessPresentationOptions): Promise - public abstract proposalAndRequestAreEqual( - proposalAttachments: ProofAttachmentFormat[], - requestAttachments: ProofAttachmentFormat[] - ): boolean + // credentials for request + getCredentialsForRequest( + agentContext: AgentContext, + options: ProofFormatGetCredentialsForRequestOptions + ): Promise> + selectCredentialsForRequest( + agentContext: AgentContext, + options: ProofFormatSelectCredentialsForRequestOptions + ): Promise> - public abstract supportsFormat(formatIdentifier: string): boolean + // auto accept methods + shouldAutoRespondToProposal( + agentContext: AgentContext, + options: ProofFormatAutoRespondProposalOptions + ): Promise + shouldAutoRespondToRequest( + agentContext: AgentContext, + options: ProofFormatAutoRespondRequestOptions + ): Promise + shouldAutoRespondToPresentation( + agentContext: AgentContext, + options: ProofFormatAutoRespondPresentationOptions + ): Promise - public abstract createRequestAsResponse( - options: CreateRequestAsResponseOptions<[IndyProofFormat]> - ): Promise + supportsFormat(formatIdentifier: string): boolean } diff --git a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts index 25731e43c8..db7923bafc 100644 --- a/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts +++ b/packages/core/src/modules/proofs/formats/ProofFormatServiceOptions.ts @@ -1,23 +1,35 @@ -import type { ProofFormat } from './ProofFormat' +import type { ProofFormat, ProofFormatCredentialForRequestPayload, ProofFormatPayload } from './ProofFormat' import type { ProofFormatService } from './ProofFormatService' import type { Attachment } from '../../../decorators/attachment/Attachment' import type { ProofFormatSpec } from '../models/ProofFormatSpec' +import type { ProofExchangeRecord } from '../repository/ProofExchangeRecord' /** - * Get the service map for usage in the proofs module. Will return a type mapping of protocol version to service. + * Infer the {@link ProofFormat} based on a {@link ProofFormatService}. + * + * It does this by extracting the `ProofFormat` generic from the `ProofFormatService`. * * @example * ``` - * type FormatServiceMap = ProofFormatServiceMap<[IndyProofFormat]> + * // TheProofFormat is now equal to IndyProofFormat + * type TheProofFormat = ExtractProofFormat + * ``` * - * // equal to - * type FormatServiceMap = { - * indy: ProofFormatServiceMap + * Because the `IndyProofFormatService` is defined as follows: + * ``` + * class IndyProofFormatService implements ProofFormatService { * } * ``` */ -export type ProofFormatServiceMap = { - [PF in PFs[number] as PF['formatKey']]: ProofFormatService +export type ExtractProofFormat = Type extends ProofFormatService ? ProofFormat : never + +/** + * Infer an array of {@link ProofFormat} types based on an array of {@link ProofFormatService} types. + * + * This is based on {@link ExtractProofFormat}, but allows to handle arrays. + */ +export type ExtractProofFormats = { + [PF in keyof PFs]: ExtractProofFormat } /** @@ -29,3 +41,85 @@ export interface ProofFormatCreateReturn { format: ProofFormatSpec attachment: Attachment } + +/** + * Base type for all proof process methods. + */ +export interface ProofFormatProcessOptions { + attachment: Attachment + proofRecord: ProofExchangeRecord +} + +export interface ProofFormatProcessPresentationOptions extends ProofFormatProcessOptions { + requestAttachment: Attachment +} + +export interface ProofFormatCreateProposalOptions { + proofRecord: ProofExchangeRecord + proofFormats: ProofFormatPayload<[PF], 'createProposal'> + attachmentId?: string +} + +export interface ProofFormatAcceptProposalOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload<[PF], 'acceptProposal'> + attachmentId?: string + + proposalAttachment: Attachment +} + +export interface FormatCreateRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats: ProofFormatPayload<[PF], 'createRequest'> + attachmentId?: string +} + +export interface ProofFormatAcceptRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload<[PF], 'acceptRequest'> + attachmentId?: string + + requestAttachment: Attachment + proposalAttachment?: Attachment +} + +export interface ProofFormatGetCredentialsForRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload<[PF], 'getCredentialsForRequest', 'input'> + + requestAttachment: Attachment + proposalAttachment?: Attachment +} + +export type ProofFormatGetCredentialsForRequestReturn = + PF['proofFormats']['getCredentialsForRequest']['output'] + +export interface ProofFormatSelectCredentialsForRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload<[PF], 'selectCredentialsForRequest', 'input'> + + requestAttachment: Attachment + proposalAttachment?: Attachment +} + +export type ProofFormatSelectCredentialsForRequestReturn = + PF['proofFormats']['selectCredentialsForRequest']['output'] + +export interface ProofFormatAutoRespondProposalOptions { + proofRecord: ProofExchangeRecord + proposalAttachment: Attachment + requestAttachment: Attachment +} + +export interface ProofFormatAutoRespondRequestOptions { + proofRecord: ProofExchangeRecord + requestAttachment: Attachment + proposalAttachment: Attachment +} + +export interface ProofFormatAutoRespondPresentationOptions { + proofRecord: ProofExchangeRecord + proposalAttachment?: Attachment + requestAttachment: Attachment + presentationAttachment: Attachment +} diff --git a/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts b/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts new file mode 100644 index 0000000000..a81e4e5553 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts @@ -0,0 +1,3 @@ +import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' + +export class InvalidEncodedValueError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts b/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts new file mode 100644 index 0000000000..a00abc40cb --- /dev/null +++ b/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts @@ -0,0 +1,3 @@ +import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' + +export class MissingIndyProofMessageError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/index.ts b/packages/core/src/modules/proofs/formats/index.ts index efb4e8a6ab..08ece3aa21 100644 --- a/packages/core/src/modules/proofs/formats/index.ts +++ b/packages/core/src/modules/proofs/formats/index.ts @@ -1,6 +1,9 @@ -export * from './indy' -export * from './models' export * from './ProofFormat' -export * from './ProofFormatConstants' export * from './ProofFormatService' export * from './ProofFormatServiceOptions' + +export * from './indy' + +import * as ProofFormatServiceOptions from './ProofFormatServiceOptions' + +export { ProofFormatServiceOptions } diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts index ae58b2db75..a6afce160a 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts @@ -1,46 +1,75 @@ -import type { RequestedAttribute } from './models/RequestedAttribute' -import type { IndyRequestedCredentialsOptions } from './models/RequestedCredentials' -import type { RequestedPredicate } from './models/RequestedPredicate' -import type { PresentationPreviewAttribute, PresentationPreviewPredicate } from '../../protocol/v1' +import type { ProofAttributeInfoOptions, ProofPredicateInfoOptions } from './models' +import type { RequestedAttributeOptions } from './models/RequestedAttribute' +import type { RequestedPredicateOptions } from './models/RequestedPredicate' +import type { V1PresentationPreviewAttributeOptions, V1PresentationPreviewPredicateOptions } from '../../protocol/v1' import type { ProofFormat } from '../ProofFormat' -import type { IndyRequestProofFormat } from '../indy/IndyProofFormatsServiceOptions' import type { IndyProof, IndyProofRequest } from 'indy-sdk' +/** + * Interface for creating an indy proof proposal. + */ export interface IndyProposeProofFormat { - attributes?: PresentationPreviewAttribute[] - predicates?: PresentationPreviewPredicate[] - nonce?: string name?: string version?: string + attributes?: V1PresentationPreviewAttributeOptions[] + predicates?: V1PresentationPreviewPredicateOptions[] } -export interface IndyRequestedCredentialsFormat { - requestedAttributes: Record - requestedPredicates: Record +/** + * Interface for creating an indy proof request. + */ +export interface IndyRequestProofFormat { + name: string + version: string + // TODO: update to AnonCredsNonRevokedInterval when moving to AnonCreds package + nonRevoked?: { from?: number; to?: number } + requestedAttributes?: Record + requestedPredicates?: Record +} + +/** + * Interface for accepting an indy proof request. + */ +export type IndyAcceptProofRequestFormat = Partial + +export interface IndySelectedCredentialsForProofRequest { + requestedAttributes: Record + requestedPredicates: Record selfAttestedAttributes: Record } -export interface IndyRetrievedCredentialsFormat { - requestedAttributes: Record - requestedPredicates: Record +/** + * Interface for getting credentials for an indy proof request. + */ +export interface IndyCredentialsForProofRequest { + attributes: Record + predicates: Record +} + +export interface IndyGetCredentialsForProofRequestOptions { + filterByNonRevocationRequirements?: boolean } export interface IndyProofFormat extends ProofFormat { formatKey: 'indy' - proofRecordType: 'indy' + proofFormats: { createProposal: IndyProposeProofFormat - acceptProposal: unknown + acceptProposal: { + name?: string + version?: string + } createRequest: IndyRequestProofFormat - acceptRequest: unknown - createPresentation: IndyRequestedCredentialsOptions - acceptPresentation: unknown - createProposalAsResponse: IndyProposeProofFormat - createOutOfBandRequest: unknown - createRequestAsResponse: IndyRequestProofFormat - createProofRequestFromProposal: IndyRequestProofFormat - requestCredentials: IndyRequestedCredentialsFormat - retrieveCredentials: IndyRetrievedCredentialsFormat + acceptRequest: IndyAcceptProofRequestFormat + + getCredentialsForRequest: { + input: IndyGetCredentialsForProofRequestOptions + output: IndyCredentialsForProofRequest + } + selectCredentialsForRequest: { + input: IndyGetCredentialsForProofRequestOptions + output: IndySelectedCredentialsForProofRequest + } } formatData: { diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts index 9730e6aa8d..924f9dcb62 100644 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts +++ b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts @@ -1,293 +1,211 @@ -import type { IndyProofFormat, IndyProposeProofFormat } from './IndyProofFormat' -import type { GetRequestedCredentialsFormat } from './IndyProofFormatsServiceOptions' -import type { AgentContext } from '../../../../agent' -import type { Logger } from '../../../../logger' import type { - CreateRequestAsResponseOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../../models/ProofServiceOptions' -import type { ProofRequestFormats } from '../../models/SharedOptions' -import type { PresentationPreviewAttribute } from '../../protocol/v1/models' -import type { ProofAttachmentFormat } from '../models/ProofAttachmentFormat' + IndyCredentialsForProofRequest, + IndyGetCredentialsForProofRequestOptions, + IndyProofFormat, + IndySelectedCredentialsForProofRequest, +} from './IndyProofFormat' +import type { ProofAttributeInfo, ProofPredicateInfo } from './models' +import type { AgentContext } from '../../../../agent' +import type { ProofFormatService } from '../ProofFormatService' import type { - CreatePresentationFormatsOptions, - CreateProofAttachmentOptions, - FormatCreateProofProposalOptions, - CreateRequestAttachmentOptions, - CreateRequestOptions, - FormatCreatePresentationOptions, - ProcessPresentationOptions, - ProcessProposalOptions, - ProcessRequestOptions, - VerifyProofOptions, -} from '../models/ProofFormatServiceOptions' -import type { CredDef, IndyProof, Schema } from 'indy-sdk' - -import { Lifecycle, scoped } from 'tsyringe' - -import { AgentConfig } from '../../../../agent/AgentConfig' + ProofFormatCreateProposalOptions, + ProofFormatCreateReturn, + ProofFormatAcceptProposalOptions, + ProofFormatAcceptRequestOptions, + ProofFormatAutoRespondProposalOptions, + ProofFormatAutoRespondRequestOptions, + ProofFormatGetCredentialsForRequestOptions, + ProofFormatGetCredentialsForRequestReturn, + ProofFormatSelectCredentialsForRequestOptions, + ProofFormatSelectCredentialsForRequestReturn, + ProofFormatProcessOptions, + FormatCreateRequestOptions, + ProofFormatProcessPresentationOptions, +} from '../ProofFormatServiceOptions' +import type { CredDef, IndyProof, IndyProofRequest, Schema } from 'indy-sdk' + import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { ConsoleLogger, LogLevel } from '../../../../logger' -import { DidCommMessageRepository } from '../../../../storage/didcomm/DidCommMessageRepository' -import { checkProofRequestForDuplicates, deepEquality } from '../../../../utils' import { JsonEncoder } from '../../../../utils/JsonEncoder' import { JsonTransformer } from '../../../../utils/JsonTransformer' import { MessageValidator } from '../../../../utils/MessageValidator' -import { uuid } from '../../../../utils/uuid' -import { IndyWallet } from '../../../../wallet/IndyWallet' import { IndyCredential, IndyCredentialInfo } from '../../../credentials' import { IndyCredentialUtils } from '../../../credentials/formats/indy/IndyCredentialUtils' -import { IndyHolderService, IndyVerifierService, IndyRevocationService } from '../../../indy' +import { IndyVerifierService, IndyHolderService, IndyRevocationService } from '../../../indy' import { IndyLedgerService } from '../../../ledger' import { ProofFormatSpec } from '../../models/ProofFormatSpec' -import { PartialProof, PresentationPreview } from '../../protocol/v1/models' -import { - V2_INDY_PRESENTATION_REQUEST, - V2_INDY_PRESENTATION_PROPOSAL, - V2_INDY_PRESENTATION, -} from '../ProofFormatConstants' -import { ProofFormatService } from '../ProofFormatService' import { InvalidEncodedValueError } from './errors/InvalidEncodedValueError' -import { MissingIndyProofMessageError } from './errors/MissingIndyProofMessageError' -import { - AttributeFilter, - ProofAttributeInfo, - ProofPredicateInfo, - RequestedAttribute, - RequestedPredicate, -} from './models' +import { RequestedAttribute, RequestedPredicate } from './models' +import { PartialProof } from './models/PartialProof' import { ProofRequest } from './models/ProofRequest' import { RequestedCredentials } from './models/RequestedCredentials' -import { RetrievedCredentials } from './models/RetrievedCredentials' +import { areIndyProofRequestsEqual, assertNoDuplicateGroupsNamesInProofRequest, createRequestFromPreview } from './util' import { sortRequestedCredentials } from './util/sortRequestedCredentials' -@scoped(Lifecycle.ContainerScoped) -export class IndyProofFormatService extends ProofFormatService { - private indyHolderService: IndyHolderService - private indyVerifierService: IndyVerifierService - private indyRevocationService: IndyRevocationService - private ledgerService: IndyLedgerService - private logger: Logger - private wallet: IndyWallet - - public constructor( - agentConfig: AgentConfig, - indyHolderService: IndyHolderService, - indyVerifierService: IndyVerifierService, - indyRevocationService: IndyRevocationService, - ledgerService: IndyLedgerService, - didCommMessageRepository: DidCommMessageRepository, - wallet: IndyWallet - ) { - super(didCommMessageRepository, agentConfig) - this.indyHolderService = indyHolderService - this.indyVerifierService = indyVerifierService - this.indyRevocationService = indyRevocationService - this.ledgerService = ledgerService - this.wallet = wallet - this.logger = new ConsoleLogger(LogLevel.off) - } +const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' +const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' +const V2_INDY_PRESENTATION = 'hlindy/proof@v2.0' + +export class IndyProofFormatService implements ProofFormatService { public readonly formatKey = 'indy' as const - public readonly proofRecordType = 'indy' as const - private createRequestAttachment(options: CreateRequestAttachmentOptions): ProofAttachmentFormat { + public async createProposal( + agentContext: AgentContext, + { attachmentId, proofFormats }: ProofFormatCreateProposalOptions + ): Promise { const format = new ProofFormatSpec({ - attachmentId: options.id, - format: V2_INDY_PRESENTATION_REQUEST, + format: V2_INDY_PRESENTATION_PROPOSAL, + attachmentId, }) - const request = new ProofRequest(options.proofRequestOptions) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(request) + const indyFormat = proofFormats.indy + if (!indyFormat) { + throw Error('Missing indy format to create proposal attachment format') + } - const attachment = new Attachment({ - id: options.id, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(request), - }), + const proofRequest = createRequestFromPreview({ + attributes: indyFormat.attributes ?? [], + predicates: indyFormat.predicates ?? [], + name: indyFormat.name ?? 'Proof request', + version: indyFormat.version ?? '1.0', + nonce: await agentContext.wallet.generateNonce(), }) - return { format, attachment } + const attachment = this.getFormatData(proofRequest.toJSON(), format.attachmentId) + + return { attachment, format } } - private async createProofAttachment(options: CreateProofAttachmentOptions): Promise { - const format = new ProofFormatSpec({ - attachmentId: options.id, - format: V2_INDY_PRESENTATION_PROPOSAL, - }) + public async processProposal(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { + const proposalJson = attachment.getDataAsJson() - const request = new ProofRequest(options.proofProposalOptions) - MessageValidator.validateSync(request) + // fromJSON also validates + const proposal = JsonTransformer.fromJSON(proposalJson, ProofRequest) - const attachment = new Attachment({ - id: options.id, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(JsonTransformer.toJSON(request)), - }), - }) - return { format, attachment } + // Assert attribute and predicate (group) names do not match + assertNoDuplicateGroupsNamesInProofRequest(proposal) } - public async createProposal(options: FormatCreateProofProposalOptions): Promise { - if (!options.formats.indy) { - throw Error('Missing indy format to create proposal attachment format') - } - const proofRequest = await this.createRequestFromPreview(options.formats.indy) - - return await this.createProofAttachment({ - id: options.id ?? uuid(), - proofProposalOptions: proofRequest, + public async acceptProposal( + agentContext: AgentContext, + { proposalAttachment, attachmentId }: ProofFormatAcceptProposalOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION_REQUEST, + attachmentId, }) - } - public async processProposal(options: ProcessProposalOptions): Promise { - const proofProposalJson = options.proposal.attachment.getDataAsJson() + const proposalJson = proposalAttachment.getDataAsJson() - // Assert attachment - if (!proofProposalJson) { - throw new AriesFrameworkError( - `Missing required base64 or json encoded attachment data for presentation proposal with thread id ${options.record?.threadId}` - ) - } + // The proposal and request formats are the same, so we can just use the proposal + const request = JsonTransformer.fromJSON(proposalJson, ProofRequest) - const proposalMessage = JsonTransformer.fromJSON(proofProposalJson, ProofRequest) + // We never want to reuse the nonce from the proposal, as this will allow replay attacks + request.nonce = await agentContext.wallet.generateNonce() - MessageValidator.validateSync(proposalMessage) - } - - public async createRequestAsResponse( - options: CreateRequestAsResponseOptions<[IndyProofFormat]> - ): Promise { - if (!options.proofFormats.indy) { - throw Error('Missing indy format to create proposal attachment format') - } + const attachment = this.getFormatData(request.toJSON(), format.attachmentId) - const id = options.id ?? uuid() + return { attachment, format } + } + public async createRequest( + agentContext: AgentContext, + { attachmentId, proofFormats }: FormatCreateRequestOptions + ): Promise { const format = new ProofFormatSpec({ - attachmentId: id, format: V2_INDY_PRESENTATION_REQUEST, + attachmentId, }) - const attachment = new Attachment({ - id: id, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(options.proofFormats.indy), - }), + const indyFormat = proofFormats.indy + if (!indyFormat) { + throw Error('Missing indy format in create request attachment format') + } + + const request = new ProofRequest({ + name: indyFormat.name, + version: indyFormat.version, + nonce: await agentContext.wallet.generateNonce(), + requestedAttributes: indyFormat.requestedAttributes, + requestedPredicates: indyFormat.requestedPredicates, + nonRevoked: indyFormat.nonRevoked, }) - return { format, attachment } - } - public async createRequest(options: CreateRequestOptions): Promise { - if (!options.formats.indy) { - throw new AriesFrameworkError('Missing indy format to create proof request attachment format.') - } + // Validate to make sure user provided correct input + MessageValidator.validateSync(request) + assertNoDuplicateGroupsNamesInProofRequest(request) - const indyFormat = options.formats.indy + const attachment = this.getFormatData(request.toJSON(), format.attachmentId) - return this.createRequestAttachment({ - id: options.id ?? uuid(), - proofRequestOptions: { - ...indyFormat, - name: indyFormat.name ?? 'proof-request', - version: indyFormat.version ?? '1.0', - nonce: indyFormat.nonce ?? (await this.wallet.generateNonce()), - }, - }) + return { attachment, format } } - public async processRequest(options: ProcessRequestOptions): Promise { - const proofRequestJson = options.requestAttachment.attachment.getDataAsJson() + public async processRequest(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { + const requestJson = attachment.getDataAsJson() - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - // Assert attachment - if (!proofRequest) { - throw new AriesFrameworkError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${options.record?.threadId}` - ) - } - MessageValidator.validateSync(proofRequest) + // fromJSON also validates + const proofRequest = JsonTransformer.fromJSON(requestJson, ProofRequest) // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) + assertNoDuplicateGroupsNamesInProofRequest(proofRequest) } - public async createPresentation( + public async acceptRequest( agentContext: AgentContext, - options: FormatCreatePresentationOptions - ): Promise { - // Extract proof request from attachment - const proofRequestJson = options.attachment.getDataAsJson() ?? null - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - // verify everything is there - if (!options.proofFormats.indy) { - throw new AriesFrameworkError('Missing indy format to create proof presentation attachment format.') - } - - const requestedCredentials = new RequestedCredentials({ - requestedAttributes: options.proofFormats.indy.requestedAttributes, - requestedPredicates: options.proofFormats.indy.requestedPredicates, - selfAttestedAttributes: options.proofFormats.indy.selfAttestedAttributes, + { proofFormats, requestAttachment, attachmentId }: ProofFormatAcceptRequestOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION, + attachmentId, }) - const proof = await this.createProof(agentContext, proofRequest, requestedCredentials) + const indyFormat = proofFormats?.indy - const attachmentId = options.id ?? uuid() + const requestJson = requestAttachment.getDataAsJson() + const proofRequest = JsonTransformer.fromJSON(requestJson, ProofRequest) - const format = new ProofFormatSpec({ - attachmentId, - format: V2_INDY_PRESENTATION, - }) + let requestedCredentials: RequestedCredentials - const attachment = new Attachment({ - id: attachmentId, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(proof), - }), - }) - return { format, attachment } - } + if (indyFormat) { + requestedCredentials = new RequestedCredentials({ + requestedAttributes: indyFormat.requestedAttributes, + requestedPredicates: indyFormat.requestedPredicates, + selfAttestedAttributes: indyFormat.selfAttestedAttributes, + }) - public async processPresentation(agentContext: AgentContext, options: ProcessPresentationOptions): Promise { - const requestFormat = options.formatAttachments.request.find( - (x) => x.format.format === V2_INDY_PRESENTATION_REQUEST - ) + // Validate to make sure user provided correct input + MessageValidator.validateSync(requestedCredentials) + } else { + const selectedCredentials = await this._selectCredentialsForRequest(agentContext, proofRequest, { + filterByNonRevocationRequirements: true, + }) - if (!requestFormat) { - throw new MissingIndyProofMessageError( - 'Missing Indy Proof Request format while trying to process an Indy proof presentation.' - ) + requestedCredentials = new RequestedCredentials({ + requestedAttributes: selectedCredentials.requestedAttributes, + requestedPredicates: selectedCredentials.requestedPredicates, + selfAttestedAttributes: selectedCredentials.selfAttestedAttributes, + }) } - const proofFormat = options.formatAttachments.presentation.find((x) => x.format.format === V2_INDY_PRESENTATION) + const proof = await this.createProof(agentContext, proofRequest, requestedCredentials) + const attachment = this.getFormatData(proof, format.attachmentId) - if (!proofFormat) { - throw new MissingIndyProofMessageError( - 'Missing Indy Proof Presentation format while trying to process an Indy proof presentation.' - ) + return { + attachment, + format, } - - return await this.verifyProof(agentContext, { request: requestFormat.attachment, proof: proofFormat.attachment }) } - public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { - if (!options) { - throw new AriesFrameworkError('No Indy proof was provided.') - } - const proofRequestJson = options.request.getDataAsJson() ?? null - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + public async processPresentation( + agentContext: AgentContext, + { requestAttachment, attachment }: ProofFormatProcessPresentationOptions + ): Promise { + const indyVerifierService = agentContext.dependencyManager.resolve(IndyVerifierService) - const proofJson = options.proof.getDataAsJson() ?? null + const proofRequestJson = requestAttachment.getDataAsJson() + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + const proofJson = attachment.getDataAsJson() const proof = JsonTransformer.fromJSON(proofJson, PartialProof) for (const [referent, attribute] of proof.requestedProof.revealedAttributes.entries()) { @@ -310,7 +228,7 @@ export class IndyProofFormatService extends ProofFormatService { new Set(proof.identifiers.map((i) => i.credentialDefinitionId)) ) - return await this.indyVerifierService.verifyProof(agentContext, { + return await indyVerifierService.verifyProof(agentContext, { proofRequest: proofRequest.toJSON(), proof: proofJson, schemas, @@ -318,116 +236,95 @@ export class IndyProofFormatService extends ProofFormatService { }) } - public supportsFormat(formatIdentifier: string): boolean { - const supportedFormats = [V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION] - return supportedFormats.includes(formatIdentifier) + public async getCredentialsForRequest( + agentContext: AgentContext, + { requestAttachment, proofFormats }: ProofFormatGetCredentialsForRequestOptions + ): Promise> { + const proofRequestJson = requestAttachment.getDataAsJson() + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + + // Set default values + const { filterByNonRevocationRequirements = true } = proofFormats?.indy ?? {} + + const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequest, { + filterByNonRevocationRequirements, + }) + + return credentialsForRequest } - /** - * Compare presentation attrs with request/proposal attrs (auto-accept) - * - * @param proposalAttachments attachment data from the proposal - * @param requestAttachments attachment data from the request - * @returns boolean value - */ - public proposalAndRequestAreEqual( - proposalAttachments: ProofAttachmentFormat[], - requestAttachments: ProofAttachmentFormat[] - ) { - const proposalAttachment = proposalAttachments.find( - (x) => x.format.format === V2_INDY_PRESENTATION_PROPOSAL - )?.attachment - const requestAttachment = requestAttachments.find( - (x) => x.format.format === V2_INDY_PRESENTATION_REQUEST - )?.attachment - - if (!proposalAttachment) { - throw new AriesFrameworkError('Proposal message has no attachment linked to it') - } + public async selectCredentialsForRequest( + agentContext: AgentContext, + { requestAttachment, proofFormats }: ProofFormatSelectCredentialsForRequestOptions + ): Promise> { + const proofRequestJson = requestAttachment.getDataAsJson() + const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - if (!requestAttachment) { - throw new AriesFrameworkError('Request message has no attachment linked to it') - } + // Set default values + const { filterByNonRevocationRequirements = true } = proofFormats?.indy ?? {} - const proposalAttachmentJson = proposalAttachment.getDataAsJson() - const proposalAttachmentData = JsonTransformer.fromJSON(proposalAttachmentJson, ProofRequest) + const selectedCredentials = this._selectCredentialsForRequest(agentContext, proofRequest, { + filterByNonRevocationRequirements, + }) - const requestAttachmentJson = requestAttachment.getDataAsJson() - const requestAttachmentData = JsonTransformer.fromJSON(requestAttachmentJson, ProofRequest) + return selectedCredentials + } - if ( - deepEquality(proposalAttachmentData.requestedAttributes, requestAttachmentData.requestedAttributes) && - deepEquality(proposalAttachmentData.requestedPredicates, requestAttachmentData.requestedPredicates) - ) { - return true - } + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + { proposalAttachment, requestAttachment }: ProofFormatAutoRespondProposalOptions + ): Promise { + const proposalJson = proposalAttachment.getDataAsJson() + const requestJson = requestAttachment.getDataAsJson() + + const areRequestsEqual = areIndyProofRequestsEqual(proposalJson, requestJson) + agentContext.config.logger.debug(`Indy request and proposal are are equal: ${areRequestsEqual}`, { + proposalJson, + requestJson, + }) - return false + return areRequestsEqual } - /** - * Build credential definitions object needed to create and verify proof objects. - * - * Creates object with `{ credentialDefinitionId: CredentialDefinition }` mapping - * - * @param credentialDefinitionIds List of credential definition ids - * @returns Object containing credential definitions for specified credential definition ids - * - */ - private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { - const credentialDefinitions: { [key: string]: CredDef } = {} + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + { proposalAttachment, requestAttachment }: ProofFormatAutoRespondRequestOptions + ): Promise { + const proposalJson = proposalAttachment.getDataAsJson() + const requestJson = requestAttachment.getDataAsJson() - for (const credDefId of credentialDefinitionIds) { - const credDef = await this.ledgerService.getCredentialDefinition(agentContext, credDefId) - credentialDefinitions[credDefId] = credDef - } + return areIndyProofRequestsEqual(proposalJson, requestJson) + } - return credentialDefinitions + public async shouldAutoRespondToPresentation(): Promise { + // The presentation is already verified in processPresentation, so we can just return true here. + // It's only an ack, so it's just that we received the presentation. + return true } - public async getRequestedCredentialsForProofRequest( + public supportsFormat(formatIdentifier: string): boolean { + const supportedFormats = [V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION] + return supportedFormats.includes(formatIdentifier) + } + + private async _getCredentialsForRequest( agentContext: AgentContext, - options: GetRequestedCredentialsFormat - ): Promise> { - const retrievedCredentials = new RetrievedCredentials({}) - const { attachment, presentationProposal } = options - const filterByNonRevocationRequirements = options.config?.filterByNonRevocationRequirements + proofRequest: ProofRequest, + options: IndyGetCredentialsForProofRequestOptions + ): Promise { + const credentialsForProofRequest: IndyCredentialsForProofRequest = { + attributes: {}, + predicates: {}, + } - const proofRequestJson = attachment.getDataAsJson() ?? null - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) + const proofRequestJson = proofRequest.toJSON() for (const [referent, requestedAttribute] of proofRequest.requestedAttributes.entries()) { - let credentialMatch: IndyCredential[] = [] - const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) + const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequestJson, referent) - // If we have exactly one credential, or no proposal to pick preferences - // on the credentials to use, we will use the first one - if (credentials.length === 1 || !presentationProposal) { - credentialMatch = credentials - } - // If we have a proposal we will use that to determine the credentials to use - else { - const names = requestedAttribute.names ?? [requestedAttribute.name] - - // Find credentials that matches all parameters from the proposal - credentialMatch = credentials.filter((credential) => { - const { attributes, credentialDefinitionId } = credential.credentialInfo - - // Check if credentials matches all parameters from proposal - return names.every((name) => - presentationProposal.attributes.find( - (a) => - a.name === name && - a.credentialDefinitionId === credentialDefinitionId && - (!a.value || a.value === attributes[name]) - ) - ) - }) - } - - retrievedCredentials.requestedAttributes[referent] = sortRequestedCredentials( + credentialsForProofRequest.attributes[referent] = sortRequestedCredentials( await Promise.all( - credentialMatch.map(async (credential: IndyCredential) => { + credentials.map(async (credential: IndyCredential) => { const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { proofRequest, requestedItem: requestedAttribute, @@ -447,17 +344,17 @@ export class IndyProofFormatService extends ProofFormatService { // We only attach revoked state if non-revocation is requested. So if revoked is true it means // the credential is not applicable to the proof request - if (filterByNonRevocationRequirements) { - retrievedCredentials.requestedAttributes[referent] = retrievedCredentials.requestedAttributes[referent].filter( + if (options.filterByNonRevocationRequirements) { + credentialsForProofRequest.attributes[referent] = credentialsForProofRequest.attributes[referent].filter( (r) => !r.revoked ) } } for (const [referent, requestedPredicate] of proofRequest.requestedPredicates.entries()) { - const credentials = await this.getCredentialsForProofRequest(agentContext, proofRequest, referent) + const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequestJson, referent) - retrievedCredentials.requestedPredicates[referent] = sortRequestedCredentials( + credentialsForProofRequest.predicates[referent] = sortRequestedCredentials( await Promise.all( credentials.map(async (credential) => { const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { @@ -478,68 +375,64 @@ export class IndyProofFormatService extends ProofFormatService { // We only attach revoked state if non-revocation is requested. So if revoked is true it means // the credential is not applicable to the proof request - if (filterByNonRevocationRequirements) { - retrievedCredentials.requestedPredicates[referent] = retrievedCredentials.requestedPredicates[referent].filter( + if (options.filterByNonRevocationRequirements) { + credentialsForProofRequest.predicates[referent] = credentialsForProofRequest.predicates[referent].filter( (r) => !r.revoked ) } } - return { - proofFormats: { - indy: retrievedCredentials, - }, - } + return credentialsForProofRequest } - private async getCredentialsForProofRequest( + private async _selectCredentialsForRequest( agentContext: AgentContext, proofRequest: ProofRequest, - attributeReferent: string - ): Promise { - const credentialsJson = await this.indyHolderService.getCredentialsForProofRequest(agentContext, { - proofRequest: proofRequest.toJSON(), - attributeReferent, - }) - - return JsonTransformer.fromJSON(credentialsJson, IndyCredential) as unknown as IndyCredential[] - } - - public async autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions<[IndyProofFormat]> - ): Promise> { - const { proofFormats } = options - const indy = proofFormats.indy - - if (!indy) { - throw new AriesFrameworkError('No indy options provided') + options: IndyGetCredentialsForProofRequestOptions + ): Promise { + const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequest, options) + + const selectedCredentials: IndySelectedCredentialsForProofRequest = { + requestedAttributes: {}, + requestedPredicates: {}, + selfAttestedAttributes: {}, } - const requestedCredentials = new RequestedCredentials({}) - - Object.keys(indy.requestedAttributes).forEach((attributeName) => { - const attributeArray = indy.requestedAttributes[attributeName] + Object.keys(credentialsForRequest.attributes).forEach((attributeName) => { + const attributeArray = credentialsForRequest.attributes[attributeName] if (attributeArray.length === 0) { throw new AriesFrameworkError('Unable to automatically select requested attributes.') - } else { - requestedCredentials.requestedAttributes[attributeName] = attributeArray[0] } + + selectedCredentials.requestedAttributes[attributeName] = attributeArray[0] }) - Object.keys(indy.requestedPredicates).forEach((attributeName) => { - if (indy.requestedPredicates[attributeName].length === 0) { + Object.keys(credentialsForRequest.predicates).forEach((attributeName) => { + if (credentialsForRequest.predicates[attributeName].length === 0) { throw new AriesFrameworkError('Unable to automatically select requested predicates.') } else { - requestedCredentials.requestedPredicates[attributeName] = indy.requestedPredicates[attributeName][0] + selectedCredentials.requestedPredicates[attributeName] = credentialsForRequest.predicates[attributeName][0] } }) - return { - proofFormats: { - indy: requestedCredentials, - }, - } + return selectedCredentials + } + + private async getCredentialsForProofRequestReferent( + agentContext: AgentContext, + // pass as json to prevent having to transform to json on every call + proofRequestJson: IndyProofRequest, + attributeReferent: string + ): Promise { + const holderService = agentContext.dependencyManager.resolve(IndyHolderService) + + const credentialsJson = await holderService.getCredentialsForProofRequest(agentContext, { + proofRequest: proofRequestJson, + attributeReferent, + }) + + return JsonTransformer.fromJSON(credentialsJson, IndyCredential) as unknown as IndyCredential[] } /** @@ -552,16 +445,40 @@ export class IndyProofFormatService extends ProofFormatService { * */ private async getSchemas(agentContext: AgentContext, schemaIds: Set) { + const ledgerService = agentContext.dependencyManager.resolve(IndyLedgerService) + const schemas: { [key: string]: Schema } = {} for (const schemaId of schemaIds) { - const schema = await this.ledgerService.getSchema(agentContext, schemaId) + const schema = await ledgerService.getSchema(agentContext, schemaId) schemas[schemaId] = schema } return schemas } + /** + * Build credential definitions object needed to create and verify proof objects. + * + * Creates object with `{ credentialDefinitionId: CredentialDefinition }` mapping + * + * @param credentialDefinitionIds List of credential definition ids + * @returns Object containing credential definitions for specified credential definition ids + * + */ + private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { + const ledgerService = agentContext.dependencyManager.resolve(IndyLedgerService) + + const credentialDefinitions: { [key: string]: CredDef } = {} + + for (const credDefId of credentialDefinitionIds) { + const credDef = await ledgerService.getCredentialDefinition(agentContext, credDefId) + credentialDefinitions[credDefId] = credDef + } + + return credentialDefinitions + } + /** * Create indy proof from a given proof request and requested credential object. * @@ -574,6 +491,8 @@ export class IndyProofFormatService extends ProofFormatService { proofRequest: ProofRequest, requestedCredentials: RequestedCredentials ): Promise { + const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) + const credentialObjects = await Promise.all( [ ...Object.values(requestedCredentials.requestedAttributes), @@ -582,7 +501,7 @@ export class IndyProofFormatService extends ProofFormatService { if (c.credentialInfo) { return c.credentialInfo } - const credentialInfo = await this.indyHolderService.getCredential(agentContext, c.credentialId) + const credentialInfo = await indyHolderService.getCredential(agentContext, c.credentialId) return JsonTransformer.fromJSON(credentialInfo, IndyCredentialInfo) }) ) @@ -593,7 +512,7 @@ export class IndyProofFormatService extends ProofFormatService { new Set(credentialObjects.map((c) => c.credentialDefinitionId)) ) - return await this.indyHolderService.createProof(agentContext, { + return await indyHolderService.createProof(agentContext, { proofRequest: proofRequest.toJSON(), requestedCredentials: requestedCredentials, schemas, @@ -601,25 +520,6 @@ export class IndyProofFormatService extends ProofFormatService { }) } - public async createProofRequestFromProposal(options: CreatePresentationFormatsOptions): Promise { - const proofRequestJson = options.presentationAttachment.getDataAsJson() - - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - // Assert attachment - if (!proofRequest) { - throw new AriesFrameworkError(`Missing required base64 or json encoded attachment data for presentation request.`) - } - MessageValidator.validateSync(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - return { - indy: proofRequest, - } - } - private async getRevocationStatusForRequestedItem( agentContext: AgentContext, { @@ -632,13 +532,15 @@ export class IndyProofFormatService extends ProofFormatService { credential: IndyCredential } ) { + const indyRevocationService = agentContext.dependencyManager.resolve(IndyRevocationService) + const requestNonRevoked = requestedItem.nonRevoked ?? proofRequest.nonRevoked const credentialRevocationId = credential.credentialInfo.credentialRevocationId const revocationRegistryId = credential.credentialInfo.revocationRegistryId // If revocation interval is present and the credential is revocable then fetch the revocation status of credentials for display if (requestNonRevoked && credentialRevocationId && revocationRegistryId) { - this.logger.trace( + agentContext.config.logger.trace( `Presentation is requesting proof of non revocation, getting revocation status for credential`, { requestNonRevoked, @@ -648,7 +550,7 @@ export class IndyProofFormatService extends ProofFormatService { ) // Note presentation from-to's vs ledger from-to's: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals - const status = await this.indyRevocationService.getRevocationStatus( + const status = await indyRevocationService.getRevocationStatus( agentContext, credentialRevocationId, revocationRegistryId, @@ -661,89 +563,22 @@ export class IndyProofFormatService extends ProofFormatService { return { revoked: undefined, deltaTimestamp: undefined } } - public async createRequestFromPreview(indyFormat: IndyProposeProofFormat): Promise { - const preview = new PresentationPreview({ - attributes: indyFormat.attributes, - predicates: indyFormat.predicates, - }) - - const proofRequest = await this.createReferentForProofRequest(indyFormat, preview) - - return proofRequest - } - - public async createReferentForProofRequest( - indyFormat: IndyProposeProofFormat, - preview: PresentationPreview - ): Promise { - const proofRequest = new ProofRequest({ - name: indyFormat.name ?? 'proof-request', - version: indyFormat.version ?? '1.0', - nonce: indyFormat.nonce ?? (await this.wallet.generateNonce()), + /** + * Returns an object of type {@link Attachment} for use in credential exchange messages. + * It looks up the correct format identifier and encodes the data as a base64 attachment. + * + * @param data The data to include in the attach object + * @param id the attach id from the formats component of the message + */ + private getFormatData(data: unknown, id: string): Attachment { + const attachment = new Attachment({ + id, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(data), + }), }) - /** - * Create mapping of attributes by referent. This required the - * attributes to come from the same credential. - * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent - * - * { - * "referent1": [Attribute1, Attribute2], - * "referent2": [Attribute3] - * } - */ - const attributesByReferent: Record = {} - for (const proposedAttributes of preview.attributes) { - if (!proposedAttributes.referent) proposedAttributes.referent = uuid() - - const referentAttributes = attributesByReferent[proposedAttributes.referent] - - // Referent key already exist, add to list - if (referentAttributes) { - referentAttributes.push(proposedAttributes) - } - - // Referent key does not exist yet, create new entry - else { - attributesByReferent[proposedAttributes.referent] = [proposedAttributes] - } - } - - // Transform attributes by referent to requested attributes - for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { - // Either attributeName or attributeNames will be undefined - const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined - const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined - - const requestedAttribute = new ProofAttributeInfo({ - name: attributeName, - names: attributeNames, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, - }), - ], - }) - - proofRequest.requestedAttributes.set(referent, requestedAttribute) - } - - // Transform proposed predicates to requested predicates - for (const proposedPredicate of preview.predicates) { - const requestedPredicate = new ProofPredicateInfo({ - name: proposedPredicate.name, - predicateType: proposedPredicate.predicate, - predicateValue: proposedPredicate.threshold, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: proposedPredicate.credentialDefinitionId, - }), - ], - }) - - proofRequest.requestedPredicates.set(uuid(), requestedPredicate) - } - - return proofRequest + return attachment } } diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts deleted file mode 100644 index bb78139bb7..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatsServiceOptions.ts +++ /dev/null @@ -1,38 +0,0 @@ -import type { IndyRequestedCredentialsFormat } from './IndyProofFormat' -import type { ProofAttributeInfo } from '.././indy/models/ProofAttributeInfo' -import type { ProofPredicateInfo } from '.././indy/models/ProofPredicateInfo' -import type { Attachment } from '../../../../decorators/attachment/Attachment' -import type { IndyRevocationInterval } from '../../../credentials' -import type { GetRequestedCredentialsConfig } from '../../models/GetRequestedCredentialsConfig' -import type { PresentationPreview } from '../../protocol/v1/models/V1PresentationPreview' -import type { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' - -export type IndyPresentationProofFormat = IndyRequestedCredentialsFormat - -export interface IndyRequestProofFormat { - name?: string - version?: string - nonce?: string - nonRevoked?: IndyRevocationInterval - ver?: '1.0' | '2.0' - requestedAttributes?: Record | Map - requestedPredicates?: Record | Map -} - -export interface IndyVerifyProofFormat { - proofJson: Attachment - proofRequest: Attachment -} - -export interface GetRequestedCredentialsFormat { - attachment: Attachment - presentationProposal?: PresentationPreview - config?: GetRequestedCredentialsConfig -} - -export interface IndyProofRequestFromProposalOptions { - proofRecord: ProofExchangeRecord - name?: string - version?: string - nonce?: string -} diff --git a/packages/core/src/modules/proofs/__tests__/groupKeys.ts b/packages/core/src/modules/proofs/formats/indy/__tests__/groupKeys.ts similarity index 79% rename from packages/core/src/modules/proofs/__tests__/groupKeys.ts rename to packages/core/src/modules/proofs/formats/indy/__tests__/groupKeys.ts index e20144792f..3d62914aca 100644 --- a/packages/core/src/modules/proofs/__tests__/groupKeys.ts +++ b/packages/core/src/modules/proofs/formats/indy/__tests__/groupKeys.ts @@ -1,9 +1,9 @@ -import type { IndyProofFormat } from '../formats/indy/IndyProofFormat' -import type { GetFormatDataReturn } from '../models/ProofServiceOptions' +import type { GetProofFormatDataReturn } from '../../../protocol/ProofProtocolOptions' +import type { IndyProofFormat } from '../IndyProofFormat' -import { AriesFrameworkError } from '../../../error' +import { AriesFrameworkError } from '../../../../../error' -export function getGroupKeysFromIndyProofFormatData(formatData: GetFormatDataReturn<[IndyProofFormat]>): { +export function getGroupKeysFromIndyProofFormatData(formatData: GetProofFormatDataReturn<[IndyProofFormat]>): { proposeKey1: string proposeKey2: string requestKey1: string diff --git a/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts b/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts new file mode 100644 index 0000000000..f4165bdb46 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts @@ -0,0 +1,493 @@ +import type { default as Indy } from 'indy-sdk' + +import { AriesFrameworkError } from '../../../../../error' +import { AttributeFilter, PredicateType, ProofAttributeInfo, ProofPredicateInfo, ProofRequest } from '../models' +import { areIndyProofRequestsEqual, assertNoDuplicateGroupsNamesInProofRequest } from '../util' + +const proofRequest = { + name: 'Proof Request', + version: '1.0.0', + nonce: 'nonce', + ver: '1.0', + non_revoked: {}, + requested_attributes: { + a: { + names: ['name1', 'name2'], + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + schema_id: 'schema_id', + }, + ], + }, + }, + requested_predicates: { + p: { + name: 'Hello', + p_type: '<', + p_value: 10, + restrictions: [ + { + cred_def_id: 'string2', + }, + { + cred_def_id: 'string', + }, + ], + }, + }, +} satisfies Indy.IndyProofRequest + +const credDefId = '9vPXgSpQJPkJEALbLXueBp:3:CL:57753:tag1' +const nonce = 'testtesttest12345' + +describe('IndyProofFormat | util', () => { + describe('assertNoDuplicateGroupsNamesInProofRequest', () => { + test('attribute names match', () => { + const attributes = { + age1: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + age2: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const proofRequest = new ProofRequest({ + name: 'proof-request', + version: '1.0', + nonce, + requestedAttributes: attributes, + }) + + expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).not.toThrow() + }) + + test('attribute names match with predicates name', () => { + const attributes = { + attrib: new ProofAttributeInfo({ + name: 'age', + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const predicates = { + predicate: new ProofPredicateInfo({ + name: 'age', + predicateType: PredicateType.GreaterThanOrEqualTo, + predicateValue: 50, + restrictions: [ + new AttributeFilter({ + credentialDefinitionId: credDefId, + }), + ], + }), + } + + const proofRequest = new ProofRequest({ + name: 'proof-request', + version: '1.0', + nonce, + requestedAttributes: attributes, + requestedPredicates: predicates, + }) + + expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).toThrowError(AriesFrameworkError) + }) + }) + describe('areIndyProofRequestsEqual', () => { + test('does not compare name, ver, version and nonce', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + name: 'Proof Request 2', + version: '2.0.0', + nonce: 'nonce2', + ver: '2.0', + }) + ).toBe(true) + }) + + test('check top level non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + non_revoked: {}, + }) + ).toBe(true) + + // properties inside object are different + expect( + areIndyProofRequestsEqual( + { + ...proofRequest, + non_revoked: { + to: 5, + }, + }, + { + ...proofRequest, + non_revoked: { + from: 5, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + non_revoked: { + from: 5, + }, + }) + ).toBe(false) + }) + + test('ignores attribute group name differences', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + b: proofRequest.requested_attributes.a, + }, + }) + ).toBe(true) + }) + + test('ignores attribute restriction order', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [...proofRequest.requested_attributes.a.restrictions].reverse(), + }, + }, + }) + ).toBe(true) + }) + + test('ignores attribute restriction undefined vs empty array', () => { + expect( + areIndyProofRequestsEqual( + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: undefined, + }, + }, + }, + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [], + }, + }, + } + ) + ).toBe(true) + }) + + test('ignores attribute names order', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + names: ['name2', 'name1'], + }, + }, + }) + ).toBe(true) + }) + + test('checks attribute non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: {}, + }, + }, + }) + ).toBe(true) + + // properties inside object are different + expect( + areIndyProofRequestsEqual( + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + to: 5, + }, + }, + }, + }, + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + from: 5, + }, + }, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + from: 5, + }, + }, + }, + }) + ).toBe(false) + }) + + test('checks attribute restriction differences', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + cred_def_id: 'cred_def_id2', + }, + ], + }, + }, + }) + ).toBe(false) + }) + + test('checks attribute name differences', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + names: ['name3'], + }, + }, + }) + ).toBe(false) + + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + name: 'name3', + names: undefined, + }, + }, + }) + ).toBe(false) + }) + + test('ignores predicate group name differences', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + a: proofRequest.requested_predicates.p, + }, + }) + ).toBe(true) + }) + + test('ignores predicate restriction order', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [...proofRequest.requested_predicates.p.restrictions].reverse(), + }, + }, + }) + ).toBe(true) + }) + + test('ignores predicate restriction undefined vs empty array', () => { + expect( + areIndyProofRequestsEqual( + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: undefined, + }, + }, + }, + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [], + }, + }, + } + ) + ).toBe(true) + }) + + test('checks predicate restriction differences', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + cred_def_id: 'cred_def_id2', + }, + ], + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate name differences', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + name: 'name3', + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: {}, + }, + }, + }) + ).toBe(true) + + // properties inside object are different + expect( + areIndyProofRequestsEqual( + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + to: 5, + }, + }, + }, + }, + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + from: 5, + }, + }, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + from: 5, + }, + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate p_type and p_value', () => { + expect( + areIndyProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + p_type: '<', + p_value: 134134, + }, + }, + }) + ).toBe(false) + }) + }) +}) diff --git a/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts b/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts deleted file mode 100644 index 2ab9c3f15e..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/errors/MissingIndyProofMessageError.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' - -export class MissingIndyProofMessageError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/indy/errors/index.ts b/packages/core/src/modules/proofs/formats/indy/errors/index.ts index 0f0b302726..7b2373bb66 100644 --- a/packages/core/src/modules/proofs/formats/indy/errors/index.ts +++ b/packages/core/src/modules/proofs/formats/indy/errors/index.ts @@ -1,2 +1 @@ export * from './InvalidEncodedValueError' -export * from './MissingIndyProofMessageError' diff --git a/packages/core/src/modules/proofs/formats/indy/index.ts b/packages/core/src/modules/proofs/formats/indy/index.ts index c94afb8629..185c2f8afc 100644 --- a/packages/core/src/modules/proofs/formats/indy/index.ts +++ b/packages/core/src/modules/proofs/formats/indy/index.ts @@ -1,4 +1,2 @@ -export * from './errors' -export * from './models' export * from './IndyProofFormat' -export * from './IndyProofFormatsServiceOptions' +export * from './IndyProofFormatService' diff --git a/packages/core/src/modules/proofs/protocol/v1/models/PartialProof.ts b/packages/core/src/modules/proofs/formats/indy/models/PartialProof.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/models/PartialProof.ts rename to packages/core/src/modules/proofs/formats/indy/models/PartialProof.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/models/ProofAttribute.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofAttribute.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/models/ProofAttribute.ts rename to packages/core/src/modules/proofs/formats/indy/models/ProofAttribute.ts diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts index 4bf1f136b0..a67c8425ae 100644 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts @@ -5,13 +5,15 @@ import { IndyRevocationInterval } from '../../../../credentials' import { AttributeFilter } from './AttributeFilter' +export type ProofAttributeInfoOptions = ProofAttributeInfo + export class ProofAttributeInfo { - public constructor(options: ProofAttributeInfo) { + public constructor(options: ProofAttributeInfoOptions) { if (options) { this.name = options.name this.names = options.names - this.nonRevoked = options.nonRevoked - this.restrictions = options.restrictions + this.nonRevoked = options.nonRevoked ? new IndyRevocationInterval(options.nonRevoked) : undefined + this.restrictions = options.restrictions?.map((r) => new AttributeFilter(r)) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/models/ProofIdentifier.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofIdentifier.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/models/ProofIdentifier.ts rename to packages/core/src/modules/proofs/formats/indy/models/ProofIdentifier.ts diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts index 8f246746bf..48083fc54d 100644 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts @@ -6,13 +6,22 @@ import { IndyRevocationInterval } from '../../../../credentials' import { AttributeFilter } from './AttributeFilter' import { PredicateType } from './PredicateType' +export interface ProofPredicateInfoOptions { + name: string + // Also allow string value of the enum as input, to make it easier to use in the API + predicateType: PredicateType | `${PredicateType}` + predicateValue: number + nonRevoked?: IndyRevocationInterval + restrictions?: AttributeFilter[] +} + export class ProofPredicateInfo { - public constructor(options: ProofPredicateInfo) { + public constructor(options: ProofPredicateInfoOptions) { if (options) { this.name = options.name - this.nonRevoked = options.nonRevoked - this.restrictions = options.restrictions - this.predicateType = options.predicateType + this.nonRevoked = options.nonRevoked ? new IndyRevocationInterval(options.nonRevoked) : undefined + this.restrictions = options.restrictions?.map((r) => new AttributeFilter(r)) + this.predicateType = options.predicateType as PredicateType this.predicateValue = options.predicateValue } } diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts index 224169c864..b2d5cf83cc 100644 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts @@ -1,3 +1,5 @@ +import type { ProofAttributeInfoOptions } from './ProofAttributeInfo' +import type { ProofPredicateInfoOptions } from './ProofPredicateInfo' import type { IndyProofRequest } from 'indy-sdk' import { Expose, Type } from 'class-transformer' @@ -16,8 +18,8 @@ export interface ProofRequestOptions { nonce: string nonRevoked?: IndyRevocationInterval ver?: '1.0' | '2.0' - requestedAttributes?: Record | Map - requestedPredicates?: Record | Map + requestedAttributes?: Record + requestedPredicates?: Record } /** @@ -31,17 +33,22 @@ export class ProofRequest { this.name = options.name this.version = options.version this.nonce = options.nonce - this.requestedAttributes = options.requestedAttributes - ? options.requestedAttributes instanceof Map - ? options.requestedAttributes - : new Map(Object.entries(options.requestedAttributes)) - : new Map() - this.requestedPredicates = options.requestedPredicates - ? options.requestedPredicates instanceof Map - ? options.requestedPredicates - : new Map(Object.entries(options.requestedPredicates)) - : new Map() - this.nonRevoked = options.nonRevoked + + this.requestedAttributes = new Map( + Object.entries(options.requestedAttributes ?? {}).map(([key, attribute]) => [ + key, + new ProofAttributeInfo(attribute), + ]) + ) + + this.requestedPredicates = new Map( + Object.entries(options.requestedPredicates ?? {}).map(([key, predicate]) => [ + key, + new ProofPredicateInfo(predicate), + ]) + ) + + this.nonRevoked = options.nonRevoked ? new IndyRevocationInterval(options.nonRevoked) : undefined this.ver = options.ver } } diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts index 048a89cf82..21a1e9a1c3 100644 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts @@ -1,18 +1,28 @@ +import type { IndyCredentialInfoOptions } from '../../../../credentials/formats/indy/models/IndyCredentialInfo' + import { Exclude, Expose } from 'class-transformer' import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator' import { IndyCredentialInfo } from '../../../../credentials/formats/indy/models/IndyCredentialInfo' +export interface RequestedAttributeOptions { + credentialId: string + timestamp?: number + revealed: boolean + credentialInfo?: IndyCredentialInfoOptions + revoked?: boolean +} + /** * Requested Attribute for Indy proof creation */ export class RequestedAttribute { - public constructor(options: RequestedAttribute) { + public constructor(options: RequestedAttributeOptions) { if (options) { this.credentialId = options.credentialId this.timestamp = options.timestamp this.revealed = options.revealed - this.credentialInfo = options.credentialInfo + this.credentialInfo = options.credentialInfo ? new IndyCredentialInfo(options.credentialInfo) : undefined this.revoked = options.revoked } } diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts index b2824bf7bd..f515a82dee 100644 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts @@ -1,3 +1,5 @@ +import type { RequestedAttributeOptions } from './RequestedAttribute' +import type { RequestedPredicateOptions } from './RequestedPredicate' import type { IndyRequestedCredentials } from 'indy-sdk' import { Expose } from 'class-transformer' @@ -10,8 +12,8 @@ import { RequestedAttribute } from './RequestedAttribute' import { RequestedPredicate } from './RequestedPredicate' export interface IndyRequestedCredentialsOptions { - requestedAttributes?: Record - requestedPredicates?: Record + requestedAttributes?: Record + requestedPredicates?: Record selfAttestedAttributes?: Record } @@ -23,8 +25,24 @@ export interface IndyRequestedCredentialsOptions { export class RequestedCredentials { public constructor(options: IndyRequestedCredentialsOptions = {}) { if (options) { - this.requestedAttributes = options.requestedAttributes ?? {} - this.requestedPredicates = options.requestedPredicates ?? {} + const { requestedAttributes, requestedPredicates } = options + + // Create RequestedAttribute objects from options + this.requestedAttributes = {} + if (requestedAttributes) { + Object.keys(requestedAttributes).forEach((key) => { + this.requestedAttributes[key] = new RequestedAttribute(requestedAttributes[key]) + }) + } + + // Create RequestedPredicate objects from options + this.requestedPredicates = {} + if (requestedPredicates) { + Object.keys(requestedPredicates).forEach((key) => { + this.requestedPredicates[key] = new RequestedPredicate(requestedPredicates[key]) + }) + } + this.selfAttestedAttributes = options.selfAttestedAttributes ?? {} } } diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts index 9109b51a4d..d8f5e2d9d2 100644 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts @@ -1,17 +1,26 @@ +import type { IndyCredentialInfoOptions } from '../../../../credentials' + import { Exclude, Expose } from 'class-transformer' import { IsInt, IsOptional, IsString } from 'class-validator' import { IndyCredentialInfo } from '../../../../credentials' +export interface RequestedPredicateOptions { + credentialId: string + timestamp?: number + credentialInfo?: IndyCredentialInfoOptions + revoked?: boolean +} + /** * Requested Predicate for Indy proof creation */ export class RequestedPredicate { - public constructor(options: RequestedPredicate) { + public constructor(options: RequestedPredicateOptions) { if (options) { this.credentialId = options.credentialId this.timestamp = options.timestamp - this.credentialInfo = options.credentialInfo + this.credentialInfo = options.credentialInfo ? new IndyCredentialInfo(options.credentialInfo) : undefined this.revoked = options.revoked } } diff --git a/packages/core/src/modules/proofs/protocol/v1/models/RequestedProof.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedProof.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/models/RequestedProof.ts rename to packages/core/src/modules/proofs/formats/indy/models/RequestedProof.ts diff --git a/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts b/packages/core/src/modules/proofs/formats/indy/models/__tests__/ProofRequest.test.ts similarity index 87% rename from packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts rename to packages/core/src/modules/proofs/formats/indy/models/__tests__/ProofRequest.test.ts index fbfab93e5f..9d52625ece 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofRequest.test.ts +++ b/packages/core/src/modules/proofs/formats/indy/models/__tests__/ProofRequest.test.ts @@ -1,7 +1,7 @@ -import { ClassValidationError } from '../../../error/ClassValidationError' -import { JsonTransformer } from '../../../utils/JsonTransformer' -import { MessageValidator } from '../../../utils/MessageValidator' -import { ProofRequest } from '../formats/indy/models/ProofRequest' +import { ClassValidationError } from '../../../../../../error/ClassValidationError' +import { JsonTransformer } from '../../../../../../utils/JsonTransformer' +import { MessageValidator } from '../../../../../../utils/MessageValidator' +import { ProofRequest } from '../ProofRequest' describe('ProofRequest', () => { it('should successfully validate if the proof request JSON contains a valid structure', async () => { diff --git a/packages/core/src/modules/proofs/formats/indy/util.ts b/packages/core/src/modules/proofs/formats/indy/util.ts new file mode 100644 index 0000000000..b68e50a6b4 --- /dev/null +++ b/packages/core/src/modules/proofs/formats/indy/util.ts @@ -0,0 +1,252 @@ +import type { V1PresentationPreviewAttributeOptions, V1PresentationPreviewPredicateOptions } from '../../protocol' +import type { default as Indy } from 'indy-sdk' + +import { AriesFrameworkError } from '../../../../error' +import { areObjectsEqual } from '../../../../utils' +import { uuid } from '../../../../utils/uuid' + +import { ProofAttributeInfo, ProofPredicateInfo, ProofRequest } from './models' + +export function createRequestFromPreview({ + name, + version, + nonce, + attributes, + predicates, +}: { + name: string + version: string + nonce: string + attributes: V1PresentationPreviewAttributeOptions[] + predicates: V1PresentationPreviewPredicateOptions[] +}): ProofRequest { + const proofRequest = new ProofRequest({ + name, + version, + nonce, + }) + + /** + * Create mapping of attributes by referent. This required the + * attributes to come from the same credential. + * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent + * + * { + * "referent1": [Attribute1, Attribute2], + * "referent2": [Attribute3] + * } + */ + const attributesByReferent: Record = {} + for (const proposedAttributes of attributes ?? []) { + if (!proposedAttributes.referent) proposedAttributes.referent = uuid() + + const referentAttributes = attributesByReferent[proposedAttributes.referent] + + // Referent key already exist, add to list + if (referentAttributes) { + referentAttributes.push(proposedAttributes) + } + + // Referent key does not exist yet, create new entry + else { + attributesByReferent[proposedAttributes.referent] = [proposedAttributes] + } + } + + // Transform attributes by referent to requested attributes + for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { + // Either attributeName or attributeNames will be undefined + const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined + const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined + + const requestedAttribute = new ProofAttributeInfo({ + name: attributeName, + names: attributeNames, + restrictions: [ + { + credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, + }, + ], + }) + + proofRequest.requestedAttributes.set(referent, requestedAttribute) + } + + // Transform proposed predicates to requested predicates + for (const proposedPredicate of predicates ?? []) { + const requestedPredicate = new ProofPredicateInfo({ + name: proposedPredicate.name, + predicateType: proposedPredicate.predicate, + predicateValue: proposedPredicate.threshold, + restrictions: [ + { + credentialDefinitionId: proposedPredicate.credentialDefinitionId, + }, + ], + }) + + proofRequest.requestedPredicates.set(uuid(), requestedPredicate) + } + + return proofRequest +} + +/** + * Checks whether two `names` arrays are equal. The order of the names doesn't matter. + */ +function areNamesEqual(namesA: string[] | undefined, namesB: string[] | undefined) { + if (namesA === undefined) return namesB === undefined || namesB.length === 0 + if (namesB === undefined) return namesA.length === 0 + + // Check if there are any duplicates + if (new Set(namesA).size !== namesA.length || new Set(namesB).size !== namesB.length) return false + + // Check if the number of names is equal between A & B + if (namesA.length !== namesB.length) return false + + return namesA.every((a) => namesB.includes(a)) +} + +/** + * Checks whether two proof requests are semantically equal. The `name`, `version` and `nonce`, `ver` fields are ignored. + * In addition the group names don't have to be the same between the different requests. + */ +export function areIndyProofRequestsEqual(requestA: Indy.IndyProofRequest, requestB: Indy.IndyProofRequest): boolean { + // Check if the top-level non-revocation interval is equal + if (!isNonRevokedEqual(requestA.non_revoked, requestB.non_revoked)) return false + + const attributeAList = Object.values(requestA.requested_attributes) + const attributeBList = Object.values(requestB.requested_attributes) + + // Check if the number of attribute groups is equal in both requests + if (attributeAList.length !== attributeBList.length) return false + + // Check if all attribute groups in A are also in B + const attributesMatch = attributeAList.every((a) => { + // find an attribute in B that matches this attribute + const bIndex = attributeBList.findIndex((b) => { + return ( + b.name === a.name && + areNamesEqual(a.names, b.names) && + isNonRevokedEqual(a.non_revoked, b.non_revoked) && + areRestrictionsEqual(a.restrictions, b.restrictions) + ) + }) + + // Match found + if (bIndex !== -1) { + attributeBList.splice(bIndex, 1) + return true + } + + // Match not found + return false + }) + + if (!attributesMatch) return false + + const predicatesA = Object.values(requestA.requested_predicates) + const predicatesB = Object.values(requestB.requested_predicates) + + if (predicatesA.length !== predicatesB.length) return false + const predicatesMatch = predicatesA.every((a) => { + // find a predicate in B that matches this predicate + const bIndex = predicatesB.findIndex((b) => { + return ( + a.name === b.name && + a.p_type === b.p_type && + a.p_value === b.p_value && + isNonRevokedEqual(a.non_revoked, b.non_revoked) && + areRestrictionsEqual(a.restrictions, b.restrictions) + ) + }) + + if (bIndex !== -1) { + predicatesB.splice(bIndex, 1) + return true + } + + return false + }) + + if (!predicatesMatch) return false + + return true +} + +/** + * Checks whether two non-revocation intervals are semantically equal. They are considered equal if: + * - Both are undefined + * - Both are empty objects + * - One if undefined and the other is an empty object + * - Both have the same from and to values + */ +function isNonRevokedEqual( + nonRevokedA: Indy.NonRevokedInterval | undefined, + nonRevokedB: Indy.NonRevokedInterval | undefined +) { + // Having an empty non-revoked object is the same as not having one + if (nonRevokedA === undefined) + return nonRevokedB === undefined || (nonRevokedB.from === undefined && nonRevokedB.to === undefined) + if (nonRevokedB === undefined) return nonRevokedA.from === undefined && nonRevokedA.to === undefined + + return nonRevokedA.from === nonRevokedB.from && nonRevokedA.to === nonRevokedB.to +} + +/** + * Check if two restriction lists are equal. The order of the restrictions does not matter. + */ +function areRestrictionsEqual( + restrictionsA: Indy.WalletQuery[] | undefined, + restrictionsB: Indy.WalletQuery[] | undefined +) { + // Having an undefined restrictions property or an empty array is the same + if (restrictionsA === undefined) return restrictionsB === undefined || restrictionsB.length === 0 + if (restrictionsB === undefined) return restrictionsA.length === 0 + + // Clone array to not modify input object + const bList = [...restrictionsB] + + // Check if all restrictions in A are also in B + return restrictionsA.every((a) => { + const bIndex = restrictionsB.findIndex((b) => areObjectsEqual(a, b)) + + // Match found + if (bIndex !== -1) { + bList.splice(bIndex, 1) + return true + } + + // Match not found + return false + }) +} + +function attributeNamesToArray(proofRequest: ProofRequest) { + // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array + // containing all attribute names from the requested attributes. + return Array.from(proofRequest.requestedAttributes.values()).reduce( + (names, a) => [...names, ...(a.name ? [a.name] : a.names ? a.names : [])], + [] + ) +} + +function predicateNamesToArray(proofRequest: ProofRequest) { + return Array.from(new Set(Array.from(proofRequest.requestedPredicates.values()).map((a) => a.name))) +} + +function assertNoDuplicates(predicates: string[], attributeNames: string[]) { + const duplicates = predicates.filter((item) => attributeNames.indexOf(item) !== -1) + if (duplicates.length > 0) { + throw new AriesFrameworkError( + `The proof request contains duplicate predicates and attributes: ${duplicates.toString()}` + ) + } +} + +// TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. +export function assertNoDuplicateGroupsNamesInProofRequest(proofRequest: ProofRequest) { + const attributes = attributeNamesToArray(proofRequest) + const predicates = predicateNamesToArray(proofRequest) + assertNoDuplicates(predicates, attributes) +} diff --git a/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts b/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts deleted file mode 100644 index 5bc2fc881b..0000000000 --- a/packages/core/src/modules/proofs/formats/models/ProofAttachmentFormat.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { Attachment } from '../../../../decorators/attachment/Attachment' -import type { ProofFormatSpec } from '../../models/ProofFormatSpec' - -export interface ProofAttachmentFormat { - format: ProofFormatSpec - attachment: Attachment -} diff --git a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts b/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts deleted file mode 100644 index 8859c48b64..0000000000 --- a/packages/core/src/modules/proofs/formats/models/ProofFormatServiceOptions.ts +++ /dev/null @@ -1,64 +0,0 @@ -import type { ProofAttachmentFormat } from './ProofAttachmentFormat' -import type { Attachment } from '../../../../decorators/attachment/Attachment' -import type { ProposeProofFormats } from '../../models/SharedOptions' -import type { ProofExchangeRecord } from '../../repository' -import type { ProofFormat, ProofFormatPayload } from '../ProofFormat' -import type { ProofRequestOptions } from '../indy/models/ProofRequest' - -export interface CreateRequestAttachmentOptions { - id?: string - proofRequestOptions: ProofRequestOptions -} - -export interface CreateProofAttachmentOptions { - id?: string - proofProposalOptions: ProofRequestOptions -} - -export interface FormatCreateProofProposalOptions { - id?: string - formats: ProposeProofFormats -} - -export interface ProcessProposalOptions { - proposal: ProofAttachmentFormat - record?: ProofExchangeRecord -} - -export interface CreateRequestOptions { - id?: string - formats: ProposeProofFormats -} - -export interface ProcessRequestOptions { - requestAttachment: ProofAttachmentFormat - record?: ProofExchangeRecord -} - -export interface FormatCreatePresentationOptions { - id?: string - attachment: Attachment - proofFormats: ProofFormatPayload<[PF], 'createPresentation'> -} - -export interface ProcessPresentationOptions { - record: ProofExchangeRecord - formatAttachments: { - request: ProofAttachmentFormat[] - presentation: ProofAttachmentFormat[] - } -} - -export interface VerifyProofOptions { - request: Attachment - proof: Attachment -} - -export interface CreateProblemReportOptions { - proofRecord: ProofExchangeRecord - description: string -} - -export interface CreatePresentationFormatsOptions { - presentationAttachment: Attachment -} diff --git a/packages/core/src/modules/proofs/formats/models/index.ts b/packages/core/src/modules/proofs/formats/models/index.ts deleted file mode 100644 index 968a6b53ee..0000000000 --- a/packages/core/src/modules/proofs/formats/models/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './ProofAttachmentFormat' -export * from './ProofFormatServiceOptions' diff --git a/packages/core/src/modules/proofs/index.ts b/packages/core/src/modules/proofs/index.ts index 5d4f5f16c7..30eb44ba0f 100644 --- a/packages/core/src/modules/proofs/index.ts +++ b/packages/core/src/modules/proofs/index.ts @@ -1,13 +1,14 @@ export * from './errors' export * from './formats' -export * from './messages' export * from './models' export * from './protocol' export * from './repository' export * from './ProofEvents' -export * from './ProofResponseCoordinator' + +// Api export * from './ProofsApi' export * from './ProofsApiOptions' -export * from './ProofService' + +// Module export * from './ProofsModule' export * from './ProofsModuleConfig' diff --git a/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts b/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts deleted file mode 100644 index 64e60f56b2..0000000000 --- a/packages/core/src/modules/proofs/messages/PresentationAckMessage.ts +++ /dev/null @@ -1,10 +0,0 @@ -import type { ProtocolVersion } from '../../../types' -import type { AckMessageOptions } from '../../common' - -export type PresentationAckMessageOptions = AckMessageOptions - -type PresentationAckMessageType = `https://didcomm.org/present-proof/${ProtocolVersion}/ack` - -export interface PresentationAckMessage { - type: PresentationAckMessageType -} diff --git a/packages/core/src/modules/proofs/messages/index.ts b/packages/core/src/modules/proofs/messages/index.ts deleted file mode 100644 index 1f395b2d57..0000000000 --- a/packages/core/src/modules/proofs/messages/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './PresentationAckMessage' diff --git a/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts b/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts deleted file mode 100644 index 9041bbabe3..0000000000 --- a/packages/core/src/modules/proofs/models/GetRequestedCredentialsConfig.ts +++ /dev/null @@ -1,19 +0,0 @@ -export interface GetRequestedCredentialsConfig { - /** - * Whether to filter the retrieved credentials using the presentation preview. - * This configuration will only have effect if a presentation proposal message is available - * containing a presentation preview. - * - * @default false - */ - filterByPresentationPreview?: boolean - - /** - * Whether to filter the retrieved credentials using the non-revocation request in the proof request. - * This configuration will only have effect if the proof request requires proof on non-revocation of any kind. - * Default to true - * - * @default true - */ - filterByNonRevocationRequirements?: boolean -} diff --git a/packages/core/src/modules/proofs/models/ModuleOptions.ts b/packages/core/src/modules/proofs/models/ModuleOptions.ts deleted file mode 100644 index e471a243db..0000000000 --- a/packages/core/src/modules/proofs/models/ModuleOptions.ts +++ /dev/null @@ -1,22 +0,0 @@ -import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' -import type { AutoAcceptProof } from './ProofAutoAcceptType' -import type { ProposeProofFormats } from './SharedOptions' - -export interface ProofConfig { - name: string - version: string -} - -export interface NegotiateRequestOptions { - proofRecordId: string - proofFormats: ProposeProofFormats - comment?: string - autoAcceptProof?: AutoAcceptProof -} - -export interface AutoSelectCredentialsForProofRequestOptions { - proofRecordId: string - config?: GetRequestedCredentialsConfig -} - -export type GetRequestedCredentialsForProofRequest = AutoSelectCredentialsForProofRequestOptions diff --git a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts b/packages/core/src/modules/proofs/models/ProofServiceOptions.ts deleted file mode 100644 index 1a978404eb..0000000000 --- a/packages/core/src/modules/proofs/models/ProofServiceOptions.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' -import type { AutoAcceptProof } from './ProofAutoAcceptType' -import type { ConnectionRecord } from '../../connections' -import type { ProofFormat, ProofFormatPayload } from '../formats/ProofFormat' -import type { ProofExchangeRecord } from '../repository' - -export type FormatDataMessagePayload< - CFs extends ProofFormat[] = ProofFormat[], - M extends keyof ProofFormat['formatData'] = keyof ProofFormat['formatData'] -> = { - [ProofFormat in CFs[number] as ProofFormat['formatKey']]?: ProofFormat['formatData'][M] -} - -interface BaseOptions { - willConfirm?: boolean - goalCode?: string - comment?: string - autoAcceptProof?: AutoAcceptProof -} - -export interface CreateProposalOptions extends BaseOptions { - connectionRecord: ConnectionRecord - proofFormats: ProofFormatPayload - parentThreadId?: string -} - -export interface CreateProposalAsResponseOptions extends BaseOptions { - proofRecord: ProofExchangeRecord - proofFormats: ProofFormatPayload -} - -export interface CreateRequestAsResponseOptions extends BaseOptions { - id?: string - proofRecord: ProofExchangeRecord - proofFormats: ProofFormatPayload -} - -export interface CreateRequestOptions extends BaseOptions { - connectionRecord?: ConnectionRecord - proofFormats: ProofFormatPayload - parentThreadId?: string -} - -export interface CreateProofRequestFromProposalOptions extends BaseOptions { - id?: string - proofRecord: ProofExchangeRecord -} - -export interface FormatRetrievedCredentialOptions { - proofFormats: ProofFormatPayload -} - -export interface FormatRequestedCredentialReturn { - proofFormats: ProofFormatPayload -} - -export interface CreatePresentationOptions extends BaseOptions { - proofRecord: ProofExchangeRecord - proofFormats: ProofFormatPayload // - lastPresentation?: boolean -} - -export interface CreateAckOptions { - proofRecord: ProofExchangeRecord -} - -export interface GetRequestedCredentialsForProofRequestOptions { - proofRecord: ProofExchangeRecord - config?: GetRequestedCredentialsConfig -} - -export interface ProofRequestFromProposalOptions { - proofRecord: ProofExchangeRecord - proofFormats: ProofFormatPayload -} - -export interface DeleteProofOptions { - deleteAssociatedDidCommMessages?: boolean -} - -export type GetFormatDataReturn = { - proposal?: FormatDataMessagePayload - request?: FormatDataMessagePayload - presentation?: FormatDataMessagePayload -} diff --git a/packages/core/src/modules/proofs/models/SharedOptions.ts b/packages/core/src/modules/proofs/models/SharedOptions.ts deleted file mode 100644 index 18fe5ef7f3..0000000000 --- a/packages/core/src/modules/proofs/models/SharedOptions.ts +++ /dev/null @@ -1,62 +0,0 @@ -import type { GetRequestedCredentialsConfig } from './GetRequestedCredentialsConfig' -import type { IndyProposeProofFormat } from '../formats/indy/IndyProofFormat' -import type { IndyRequestProofFormat, IndyVerifyProofFormat } from '../formats/indy/IndyProofFormatsServiceOptions' -import type { ProofRequest } from '../formats/indy/models/ProofRequest' -import type { IndyRequestedCredentialsOptions } from '../formats/indy/models/RequestedCredentials' -import type { RetrievedCredentials } from '../formats/indy/models/RetrievedCredentials' - -export interface ProposeProofFormats { - // If you want to propose an indy proof without attributes or - // any of the other properties you should pass an empty object - indy?: IndyProposeProofFormat - presentationExchange?: never -} - -export interface RequestProofFormats { - indy?: IndyRequestProofFormat - presentationExchange?: never -} - -export interface CreatePresentationFormats { - indy?: IndyRequestedCredentialsOptions - presentationExchange?: never -} - -export interface AcceptProposalFormats { - indy?: IndyAcceptProposalOptions - presentationExchange?: never -} - -export interface VerifyProofFormats { - indy?: IndyVerifyProofFormat - presentationExchange?: never -} - -export interface RequestedCredentialConfigOptions { - indy?: GetRequestedCredentialsConfig - presentationExchange?: never -} - -// export interface RetrievedCredentialOptions { -// indy?: RetrievedCredentials -// presentationExchange?: undefined -// } - -export interface ProofRequestFormats { - indy?: ProofRequest - presentationExchange?: undefined -} - -// export interface RequestedCredentialsFormats { -// indy?: RequestedCredentials -// presentationExchange?: undefined -// } - -interface IndyAcceptProposalOptions { - request: ProofRequest -} - -export interface AutoSelectCredentialOptions { - indy?: RetrievedCredentials - presentationExchange?: undefined -} diff --git a/packages/core/src/modules/proofs/__tests__/ProofState.test.ts b/packages/core/src/modules/proofs/models/__tests__/ProofState.test.ts similarity index 92% rename from packages/core/src/modules/proofs/__tests__/ProofState.test.ts rename to packages/core/src/modules/proofs/models/__tests__/ProofState.test.ts index 4b67ed11d0..9cabafd183 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofState.test.ts +++ b/packages/core/src/modules/proofs/models/__tests__/ProofState.test.ts @@ -1,4 +1,4 @@ -import { ProofState } from '../models/ProofState' +import { ProofState } from '../ProofState' describe('ProofState', () => { test('state matches Present Proof 1.0 (RFC 0037) state value', () => { diff --git a/packages/core/src/modules/proofs/models/index.ts b/packages/core/src/modules/proofs/models/index.ts index a092a0ae7e..9e20094e5e 100644 --- a/packages/core/src/modules/proofs/models/index.ts +++ b/packages/core/src/modules/proofs/models/index.ts @@ -1,3 +1,2 @@ -export * from './GetRequestedCredentialsConfig' export * from './ProofAutoAcceptType' export * from './ProofState' diff --git a/packages/core/src/modules/proofs/protocol/BaseProofProtocol.ts b/packages/core/src/modules/proofs/protocol/BaseProofProtocol.ts new file mode 100644 index 0000000000..9e4e9e8b1c --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/BaseProofProtocol.ts @@ -0,0 +1,286 @@ +import type { ProofProtocol } from './ProofProtocol' +import type { + CreateProofProposalOptions, + CreateProofRequestOptions, + DeleteProofOptions, + GetProofFormatDataReturn, + CreateProofProblemReportOptions, + ProofProtocolMsgReturnType, + AcceptPresentationOptions, + AcceptProofProposalOptions, + AcceptProofRequestOptions, + GetCredentialsForRequestOptions, + GetCredentialsForRequestReturn, + NegotiateProofProposalOptions, + NegotiateProofRequestOptions, + SelectCredentialsForRequestOptions, + SelectCredentialsForRequestReturn, +} from './ProofProtocolOptions' +import type { AgentMessage } from '../../../agent/AgentMessage' +import type { FeatureRegistry } from '../../../agent/FeatureRegistry' +import type { AgentContext } from '../../../agent/context/AgentContext' +import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { DependencyManager } from '../../../plugins' +import type { Query } from '../../../storage/StorageService' +import type { ProblemReportMessage } from '../../problem-reports' +import type { ProofStateChangedEvent } from '../ProofEvents' +import type { ExtractProofFormats, ProofFormatService } from '../formats' +import type { ProofExchangeRecord } from '../repository' + +import { EventEmitter } from '../../../agent/EventEmitter' +import { DidCommMessageRepository } from '../../../storage' +import { JsonTransformer } from '../../../utils/JsonTransformer' +import { ProofEventTypes } from '../ProofEvents' +import { ProofState } from '../models/ProofState' +import { ProofRepository } from '../repository' + +export abstract class BaseProofProtocol + implements ProofProtocol +{ + public abstract readonly version: string + + public abstract register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void + + // methods for proposal + public abstract createProposal( + agentContext: AgentContext, + options: CreateProofProposalOptions + ): Promise> + public abstract processProposal(messageContext: InboundMessageContext): Promise + public abstract acceptProposal( + agentContext: AgentContext, + options: AcceptProofProposalOptions + ): Promise> + public abstract negotiateProposal( + agentContext: AgentContext, + options: NegotiateProofProposalOptions + ): Promise> + + // methods for request + public abstract createRequest( + agentContext: AgentContext, + options: CreateProofRequestOptions + ): Promise> + public abstract processRequest(messageContext: InboundMessageContext): Promise + public abstract acceptRequest( + agentContext: AgentContext, + options: AcceptProofRequestOptions + ): Promise> + public abstract negotiateRequest( + agentContext: AgentContext, + options: NegotiateProofRequestOptions + ): Promise> + + // retrieving credentials for request + public abstract getCredentialsForRequest( + agentContext: AgentContext, + options: GetCredentialsForRequestOptions + ): Promise> + public abstract selectCredentialsForRequest( + agentContext: AgentContext, + options: SelectCredentialsForRequestOptions + ): Promise> + + // methods for presentation + public abstract processPresentation(messageContext: InboundMessageContext): Promise + public abstract acceptPresentation( + agentContext: AgentContext, + options: AcceptPresentationOptions + ): Promise> + + // methods for ack + public abstract processAck(messageContext: InboundMessageContext): Promise + // method for problem report + public abstract createProblemReport( + agentContext: AgentContext, + options: CreateProofProblemReportOptions + ): Promise> + + public abstract findProposalMessage(agentContext: AgentContext, proofExchangeId: string): Promise + public abstract findRequestMessage(agentContext: AgentContext, proofExchangeId: string): Promise + public abstract findPresentationMessage( + agentContext: AgentContext, + proofExchangeId: string + ): Promise + public abstract getFormatData( + agentContext: AgentContext, + proofExchangeId: string + ): Promise>> + + public async processProblemReport( + messageContext: InboundMessageContext + ): Promise { + const { message: proofProblemReportMessage, agentContext } = messageContext + + const connection = messageContext.assertReadyConnection() + + agentContext.config.logger.debug(`Processing problem report with message id ${proofProblemReportMessage.id}`) + + const proofRecord = await this.getByThreadAndConnectionId( + agentContext, + proofProblemReportMessage.threadId, + connection.id + ) + + // Update record + proofRecord.errorMessage = `${proofProblemReportMessage.description.code}: ${proofProblemReportMessage.description.en}` + await this.updateState(agentContext, proofRecord, ProofState.Abandoned) + return proofRecord + } + + /** + * Update the record to a new state and emit an state changed event. Also updates the record + * in storage. + * + * @param proofRecord The proof record to update the state for + * @param newState The state to update to + * + */ + public async updateState(agentContext: AgentContext, proofRecord: ProofExchangeRecord, newState: ProofState) { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + agentContext.config.logger.debug( + `Updating proof record ${proofRecord.id} to state ${newState} (previous=${proofRecord.state})` + ) + + const previousState = proofRecord.state + proofRecord.state = newState + await proofRepository.update(agentContext, proofRecord) + + this.emitStateChangedEvent(agentContext, proofRecord, previousState) + } + + protected emitStateChangedEvent( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord, + previousState: ProofState | null + ) { + const eventEmitter = agentContext.dependencyManager.resolve(EventEmitter) + + const clonedProof = JsonTransformer.clone(proofRecord) + + eventEmitter.emit(agentContext, { + type: ProofEventTypes.ProofStateChanged, + payload: { + proofRecord: clonedProof, + previousState: previousState, + }, + }) + } + + /** + * Retrieve a proof record by id + * + * @param proofRecordId The proof record id + * @throws {RecordNotFoundError} If no record is found + * @return The proof record + * + */ + public getById(agentContext: AgentContext, proofRecordId: string): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.getById(agentContext, proofRecordId) + } + + /** + * Retrieve all proof records + * + * @returns List containing all proof records + */ + public getAll(agentContext: AgentContext): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.getAll(agentContext) + } + + public async findAllByQuery( + agentContext: AgentContext, + query: Query + ): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.findByQuery(agentContext, query) + } + + /** + * Find a proof record by id + * + * @param proofRecordId the proof record id + * @returns The proof record or null if not found + */ + public findById(agentContext: AgentContext, proofRecordId: string): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.findById(agentContext, proofRecordId) + } + + public async delete( + agentContext: AgentContext, + proofRecord: ProofExchangeRecord, + options?: DeleteProofOptions + ): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + await proofRepository.delete(agentContext, proofRecord) + + const deleteAssociatedDidCommMessages = options?.deleteAssociatedDidCommMessages ?? true + + if (deleteAssociatedDidCommMessages) { + const didCommMessages = await didCommMessageRepository.findByQuery(agentContext, { + associatedRecordId: proofRecord.id, + }) + for (const didCommMessage of didCommMessages) { + await didCommMessageRepository.delete(agentContext, didCommMessage) + } + } + } + + /** + * Retrieve a proof record by connection id and thread id + * + * @param connectionId The connection id + * @param threadId The thread id + * @throws {RecordNotFoundError} If no record is found + * @throws {RecordDuplicateError} If multiple records are found + * @returns The proof record + */ + public getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.getSingleByQuery(agentContext, { + connectionId, + threadId, + }) + } + + /** + * Find a proof record by connection id and thread id, returns null if not found + * + * @param connectionId The connection id + * @param threadId The thread id + * @returns The proof record + */ + public findByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return proofRepository.findSingleByQuery(agentContext, { + connectionId, + threadId, + }) + } + + public async update(agentContext: AgentContext, proofRecord: ProofExchangeRecord) { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + return await proofRepository.update(agentContext, proofRecord) + } +} diff --git a/packages/core/src/modules/proofs/protocol/ProofProtocol.ts b/packages/core/src/modules/proofs/protocol/ProofProtocol.ts new file mode 100644 index 0000000000..2065d97b35 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/ProofProtocol.ts @@ -0,0 +1,117 @@ +import type { + CreateProofProposalOptions, + CreateProofRequestOptions, + DeleteProofOptions, + GetProofFormatDataReturn, + CreateProofProblemReportOptions, + ProofProtocolMsgReturnType, + AcceptProofProposalOptions, + NegotiateProofProposalOptions, + AcceptProofRequestOptions, + NegotiateProofRequestOptions, + AcceptPresentationOptions, + GetCredentialsForRequestOptions, + GetCredentialsForRequestReturn, + SelectCredentialsForRequestOptions, + SelectCredentialsForRequestReturn, +} from './ProofProtocolOptions' +import type { AgentMessage } from '../../../agent/AgentMessage' +import type { FeatureRegistry } from '../../../agent/FeatureRegistry' +import type { AgentContext } from '../../../agent/context/AgentContext' +import type { InboundMessageContext } from '../../../agent/models/InboundMessageContext' +import type { DependencyManager } from '../../../plugins' +import type { Query } from '../../../storage/StorageService' +import type { ProblemReportMessage } from '../../problem-reports' +import type { ExtractProofFormats, ProofFormatService } from '../formats' +import type { ProofState } from '../models/ProofState' +import type { ProofExchangeRecord } from '../repository' + +export interface ProofProtocol { + readonly version: string + + // methods for proposal + createProposal( + agentContext: AgentContext, + options: CreateProofProposalOptions + ): Promise> + processProposal(messageContext: InboundMessageContext): Promise + acceptProposal( + agentContext: AgentContext, + options: AcceptProofProposalOptions + ): Promise> + negotiateProposal( + agentContext: AgentContext, + options: NegotiateProofProposalOptions + ): Promise> + + // methods for request + createRequest( + agentContext: AgentContext, + options: CreateProofRequestOptions + ): Promise> + processRequest(messageContext: InboundMessageContext): Promise + acceptRequest( + agentContext: AgentContext, + options: AcceptProofRequestOptions + ): Promise> + negotiateRequest( + agentContext: AgentContext, + options: NegotiateProofRequestOptions + ): Promise> + + // retrieving credentials for request + getCredentialsForRequest( + agentContext: AgentContext, + options: GetCredentialsForRequestOptions + ): Promise> + selectCredentialsForRequest( + agentContext: AgentContext, + options: SelectCredentialsForRequestOptions + ): Promise> + + // methods for presentation + processPresentation(messageContext: InboundMessageContext): Promise + acceptPresentation( + agentContext: AgentContext, + options: AcceptPresentationOptions + ): Promise> + + // methods for ack + processAck(messageContext: InboundMessageContext): Promise + + // method for problem report + createProblemReport( + agentContext: AgentContext, + options: CreateProofProblemReportOptions + ): Promise> + processProblemReport(messageContext: InboundMessageContext): Promise + + findProposalMessage(agentContext: AgentContext, proofExchangeId: string): Promise + findRequestMessage(agentContext: AgentContext, proofExchangeId: string): Promise + findPresentationMessage(agentContext: AgentContext, proofExchangeId: string): Promise + getFormatData( + agentContext: AgentContext, + proofExchangeId: string + ): Promise>> + + // repository methods + updateState(agentContext: AgentContext, proofRecord: ProofExchangeRecord, newState: ProofState): Promise + getById(agentContext: AgentContext, proofExchangeId: string): Promise + getAll(agentContext: AgentContext): Promise + findAllByQuery(agentContext: AgentContext, query: Query): Promise + findById(agentContext: AgentContext, proofExchangeId: string): Promise + delete(agentContext: AgentContext, proofRecord: ProofExchangeRecord, options?: DeleteProofOptions): Promise + getByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise + findByThreadAndConnectionId( + agentContext: AgentContext, + threadId: string, + connectionId?: string + ): Promise + update(agentContext: AgentContext, proofRecord: ProofExchangeRecord): Promise + + register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void +} diff --git a/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts b/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts new file mode 100644 index 0000000000..43276bfeee --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts @@ -0,0 +1,165 @@ +import type { ProofProtocol } from './ProofProtocol' +import type { AgentMessage } from '../../../agent/AgentMessage' +import type { ConnectionRecord } from '../../connections' +import type { + ExtractProofFormats, + ProofFormat, + ProofFormatCredentialForRequestPayload, + ProofFormatPayload, + ProofFormatService, +} from '../formats' +import type { AutoAcceptProof } from '../models' +import type { ProofExchangeRecord } from '../repository' + +/** + * Get the format data payload for a specific message from a list of ProofFormat interfaces and a message + * + * For an indy offer, this resolves to the proof request format as defined here: + * https://github.com/hyperledger/aries-rfcs/tree/b3a3942ef052039e73cd23d847f42947f8287da2/features/0592-indy-attachments#proof-request-format + * + * @example + * ``` + * + * type RequestFormatData = FormatDataMessagePayload<[IndyProofFormat, PresentationExchangeProofFormat], 'createRequest'> + * + * // equal to + * type RequestFormatData = { + * indy: { + * // ... payload for indy proof request attachment as defined in RFC 0592 ... + * }, + * presentationExchange: { + * // ... payload for presentation exchange request attachment as defined in RFC 0510 ... + * } + * } + * ``` + */ +export type ProofFormatDataMessagePayload< + CFs extends ProofFormat[] = ProofFormat[], + M extends keyof ProofFormat['formatData'] = keyof ProofFormat['formatData'] +> = { + [ProofFormat in CFs[number] as ProofFormat['formatKey']]?: ProofFormat['formatData'][M] +} + +/** + * Infer the {@link ProofFormat} types based on an array of {@link ProofProtocol} types. + * + * It does this by extracting the `ProofFormatServices` generic from the `ProofProtocol`, and + * then extracting the `ProofFormat` generic from each of the `ProofFormatService` types. + * + * @example + * ``` + * // TheProofFormatServices is now equal to [IndyProofFormatService] + * type TheProofFormatServices = ProofFormatsFromProtocols<[V1ProofProtocol]> + * ``` + * + * Because the `V1ProofProtocol` is defined as follows: + * ``` + * class V1ProofProtocol implements ProofProtocol<[IndyProofFormatService]> { + * } + * ``` + */ +export type ProofFormatsFromProtocols = Type[number] extends ProofProtocol< + infer ProofFormatServices +> + ? ProofFormatServices extends ProofFormatService[] + ? ExtractProofFormats + : never + : never + +export type GetProofFormatDataReturn = { + proposal?: ProofFormatDataMessagePayload + request?: ProofFormatDataMessagePayload + presentation?: ProofFormatDataMessagePayload +} + +interface BaseOptions { + goalCode?: string + comment?: string + autoAcceptProof?: AutoAcceptProof +} + +export interface CreateProofProposalOptions extends BaseOptions { + connectionRecord: ConnectionRecord + proofFormats: ProofFormatPayload, 'createProposal'> + parentThreadId?: string +} + +export interface AcceptProofProposalOptions extends BaseOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload, 'acceptProposal'> + + /** @default true */ + willConfirm?: boolean +} + +export interface NegotiateProofProposalOptions extends BaseOptions { + proofRecord: ProofExchangeRecord + proofFormats: ProofFormatPayload, 'createRequest'> + + /** @default true */ + willConfirm?: boolean +} + +export interface CreateProofRequestOptions extends BaseOptions { + // Create request can also be used for connection-less, so connection is optional + connectionRecord?: ConnectionRecord + proofFormats: ProofFormatPayload, 'createRequest'> + parentThreadId?: string + + /** @default true */ + willConfirm?: boolean +} + +export interface AcceptProofRequestOptions extends BaseOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload, 'acceptRequest'> +} + +export interface NegotiateProofRequestOptions extends BaseOptions { + proofRecord: ProofExchangeRecord + proofFormats: ProofFormatPayload, 'createProposal'> +} + +export interface GetCredentialsForRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload, 'getCredentialsForRequest', 'input'> +} + +export interface GetCredentialsForRequestReturn { + proofFormats: ProofFormatCredentialForRequestPayload, 'getCredentialsForRequest', 'output'> +} + +export interface SelectCredentialsForRequestOptions { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload< + ExtractProofFormats, + 'selectCredentialsForRequest', + 'input' + > +} + +export interface SelectCredentialsForRequestReturn { + proofFormats: ProofFormatCredentialForRequestPayload< + ExtractProofFormats, + 'selectCredentialsForRequest', + 'output' + > +} + +export interface AcceptPresentationOptions { + proofRecord: ProofExchangeRecord +} + +export interface CreateProofProblemReportOptions { + proofRecord: ProofExchangeRecord + description: string +} + +export interface ProofProtocolMsgReturnType { + message: MessageType + proofRecord: ProofExchangeRecord +} + +export interface DeleteProofOptions { + deleteAssociatedDidCommMessages?: boolean +} diff --git a/packages/core/src/modules/proofs/protocol/index.ts b/packages/core/src/modules/proofs/protocol/index.ts index 4d9da63573..db72d7287c 100644 --- a/packages/core/src/modules/proofs/protocol/index.ts +++ b/packages/core/src/modules/proofs/protocol/index.ts @@ -1,2 +1,6 @@ export * from './v1' export * from './v2' +export { ProofProtocol } from './ProofProtocol' +import * as ProofProtocolOptions from './ProofProtocolOptions' + +export { ProofProtocolOptions } diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofProtocol.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofProtocol.ts new file mode 100644 index 0000000000..5461aa8ebc --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v1/V1ProofProtocol.ts @@ -0,0 +1,1111 @@ +import type { AgentContext } from '../../../../agent' +import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { DependencyManager } from '../../../../plugins' +import type { ProblemReportMessage } from '../../../problem-reports' +import type { ProofFormatService } from '../../formats' +import type { ProofFormat } from '../../formats/ProofFormat' +import type { IndyProofFormat } from '../../formats/indy/IndyProofFormat' +import type { ProofProtocol } from '../ProofProtocol' +import type { + AcceptPresentationOptions, + AcceptProofProposalOptions, + AcceptProofRequestOptions, + CreateProofProblemReportOptions, + CreateProofProposalOptions, + CreateProofRequestOptions, + GetCredentialsForRequestOptions, + GetCredentialsForRequestReturn, + GetProofFormatDataReturn, + NegotiateProofProposalOptions, + NegotiateProofRequestOptions, + ProofProtocolMsgReturnType, + SelectCredentialsForRequestOptions, + SelectCredentialsForRequestReturn, +} from '../ProofProtocolOptions' + +import { Protocol } from '../../../../agent/models' +import { Attachment } from '../../../../decorators/attachment/Attachment' +import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' +import { JsonEncoder } from '../../../../utils' +import { JsonTransformer } from '../../../../utils/JsonTransformer' +import { MessageValidator } from '../../../../utils/MessageValidator' +import { uuid } from '../../../../utils/uuid' +import { AckStatus } from '../../../common/messages/AckMessage' +import { ConnectionService } from '../../../connections' +import { ProofsModuleConfig } from '../../ProofsModuleConfig' +import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' +import { createRequestFromPreview } from '../../formats/indy/util' +import { AutoAcceptProof } from '../../models' +import { ProofState } from '../../models/ProofState' +import { ProofRepository } from '../../repository' +import { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' +import { composeAutoAccept } from '../../utils/composeAutoAccept' +import { BaseProofProtocol } from '../BaseProofProtocol' + +import { V1PresentationProblemReportError } from './errors' +import { + V1PresentationAckHandler, + V1PresentationHandler, + V1PresentationProblemReportHandler, + V1ProposePresentationHandler, + V1RequestPresentationHandler, +} from './handlers' +import { + INDY_PROOF_ATTACHMENT_ID, + INDY_PROOF_REQUEST_ATTACHMENT_ID, + V1PresentationAckMessage, + V1PresentationMessage, + V1ProposePresentationMessage, + V1RequestPresentationMessage, +} from './messages' +import { V1PresentationProblemReportMessage } from './messages/V1PresentationProblemReportMessage' +import { V1PresentationPreview } from './models/V1PresentationPreview' + +type IndyProofFormatServiceLike = ProofFormatService + +export interface V1ProofProtocolConfig { + // indyCredentialFormat must be a service that implements the `IndyProofFormat` interface, however it doesn't + // have to be the IndyProofFormatService implementation per se. + indyProofFormat: ProofFormatService +} + +export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<[IndyProofFormatServiceLike]> { + private indyProofFormat: ProofFormatService + + public constructor({ indyProofFormat }: V1ProofProtocolConfig) { + super() + + this.indyProofFormat = indyProofFormat + } + + /** + * The version of the present proof protocol this protocol supports + */ + public readonly version = 'v1' as const + + /** + * Registers the protocol implementation (handlers, feature registry) on the agent. + */ + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { + // Register message handlers for the Issue Credential V1 Protocol + dependencyManager.registerMessageHandlers([ + new V1ProposePresentationHandler(this), + new V1RequestPresentationHandler(this), + new V1PresentationHandler(this), + new V1PresentationAckHandler(this), + new V1PresentationProblemReportHandler(this), + ]) + + // Register Present Proof V1 in feature registry, with supported roles + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/present-proof/1.0', + roles: ['prover', 'verifier'], + }) + ) + } + + public async createProposal( + agentContext: AgentContext, + { + proofFormats, + connectionRecord, + comment, + parentThreadId, + autoAcceptProof, + }: CreateProofProposalOptions<[IndyProofFormatServiceLike]> + ): Promise> { + this.assertOnlyIndyFormat(proofFormats) + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + if (!proofFormats.indy) { + throw new AriesFrameworkError('Missing indy proof format in v1 create proposal call.') + } + + const presentationProposal = new V1PresentationPreview({ + attributes: proofFormats.indy?.attributes, + predicates: proofFormats.indy?.predicates, + }) + + // validate input data from user + MessageValidator.validateSync(presentationProposal) + + // Create message + const message = new V1ProposePresentationMessage({ + presentationProposal, + comment, + }) + + if (parentThreadId) + message.setThread({ + parentThreadId, + }) + + // Create record + const proofRecord = new ProofExchangeRecord({ + connectionId: connectionRecord.id, + threadId: message.threadId, + parentThreadId: message.thread?.parentThreadId, + state: ProofState.ProposalSent, + autoAcceptProof, + protocolVersion: 'v1', + }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { proofRecord, message } + } + + public async processProposal( + messageContext: InboundMessageContext + ): Promise { + const { message: proposalMessage, connection, agentContext } = messageContext + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing presentation proposal with message id ${proposalMessage.id}`) + + let proofRecord = await this.findByThreadAndConnectionId(agentContext, proposalMessage.threadId, connection?.id) + + // Proof record already exists, this is a response to an earlier message sent by us + if (proofRecord) { + agentContext.config.logger.debug('Proof record already exists for incoming proposal') + + // Assert + proofRecord.assertState(ProofState.RequestSent) + proofRecord.assertProtocolVersion('v1') + + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + const previousSentMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + // Update record + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + await this.updateState(agentContext, proofRecord, ProofState.ProposalReceived) + } else { + agentContext.config.logger.debug('Proof record does not exists yet for incoming proposal') + + // No proof record exists with thread id + proofRecord = new ProofExchangeRecord({ + connectionId: connection?.id, + threadId: proposalMessage.threadId, + parentThreadId: proposalMessage.thread?.parentThreadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + + // Assert + connectionService.assertConnectionOrServiceDecorator(messageContext) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proposalMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Save record + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + } + + return proofRecord + } + + public async acceptProposal( + agentContext: AgentContext, + { proofRecord, proofFormats, comment, autoAcceptProof }: AcceptProofProposalOptions<[IndyProofFormatServiceLike]> + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.ProposalReceived) + if (proofFormats) this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const proposalMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const indyFormat = proofFormats?.indy + + // Create a proof request from the preview, so we can let the messages + // be handled using the indy proof format which supports RFC0592 + const requestFromPreview = createRequestFromPreview({ + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + name: indyFormat?.name ?? 'Proof Request', + version: indyFormat?.version ?? '1.0', + nonce: await agentContext.wallet.generateNonce(), + }) + + const proposalAttachment = new Attachment({ + data: { + json: JsonTransformer.toJSON(requestFromPreview), + }, + }) + + // Create message + const { attachment } = await this.indyProofFormat.acceptProposal(agentContext, { + attachmentId: INDY_PROOF_REQUEST_ATTACHMENT_ID, + proofRecord, + proposalAttachment, + }) + + const requestPresentationMessage = new V1RequestPresentationMessage({ + comment, + requestAttachments: [attachment], + }) + + requestPresentationMessage.setThread({ + threadId: proofRecord.threadId, + parentThreadId: proofRecord.parentThreadId, + }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: requestPresentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { message: requestPresentationMessage, proofRecord } + } + + public async negotiateProposal( + agentContext: AgentContext, + { proofFormats, proofRecord, comment, autoAcceptProof }: NegotiateProofProposalOptions<[IndyProofFormatServiceLike]> + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.ProposalReceived) + this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Create message + const { attachment } = await this.indyProofFormat.createRequest(agentContext, { + attachmentId: INDY_PROOF_REQUEST_ATTACHMENT_ID, + proofFormats, + proofRecord, + }) + + const requestPresentationMessage = new V1RequestPresentationMessage({ + comment, + requestAttachments: [attachment], + }) + requestPresentationMessage.setThread({ + threadId: proofRecord.threadId, + }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: requestPresentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { message: requestPresentationMessage, proofRecord } + } + + public async createRequest( + agentContext: AgentContext, + { + proofFormats, + connectionRecord, + comment, + parentThreadId, + autoAcceptProof, + }: CreateProofRequestOptions<[IndyProofFormatServiceLike]> + ): Promise> { + this.assertOnlyIndyFormat(proofFormats) + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + if (!proofFormats.indy) { + throw new AriesFrameworkError('Missing indy proof request data for v1 create request') + } + + // Create record + const proofRecord = new ProofExchangeRecord({ + connectionId: connectionRecord?.id, + threadId: uuid(), + parentThreadId, + state: ProofState.RequestSent, + autoAcceptProof, + protocolVersion: 'v1', + }) + + // Create message + const { attachment } = await this.indyProofFormat.createRequest(agentContext, { + attachmentId: INDY_PROOF_REQUEST_ATTACHMENT_ID, + proofFormats, + proofRecord, + }) + + // Construct request message + const message = new V1RequestPresentationMessage({ + id: proofRecord.threadId, + comment, + requestAttachments: [attachment], + }) + + message.setThread({ + threadId: proofRecord.threadId, + parentThreadId: proofRecord.parentThreadId, + }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { message, proofRecord } + } + + public async processRequest( + messageContext: InboundMessageContext + ): Promise { + const { message: proofRequestMessage, connection, agentContext } = messageContext + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing presentation request with id ${proofRequestMessage.id}`) + + let proofRecord = await this.findByThreadAndConnectionId(agentContext, proofRequestMessage.threadId, connection?.id) + + const requestAttachment = proofRequestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) { + throw new AriesFrameworkError( + `Indy attachment with id ${INDY_PROOF_REQUEST_ATTACHMENT_ID} not found in request message` + ) + } + + // proof record already exists, this means we are the message is sent as reply to a proposal we sent + if (proofRecord) { + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + const previousSentMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.ProposalSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + await this.indyProofFormat.processRequest(agentContext, { + attachment: requestAttachment, + proofRecord, + }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: proofRequestMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + await this.updateState(agentContext, proofRecord, ProofState.RequestReceived) + } else { + // No proof record exists with thread id + proofRecord = new ProofExchangeRecord({ + connectionId: connection?.id, + threadId: proofRequestMessage.threadId, + parentThreadId: proofRequestMessage.thread?.parentThreadId, + state: ProofState.RequestReceived, + protocolVersion: 'v1', + }) + + await this.indyProofFormat.processRequest(agentContext, { + attachment: requestAttachment, + proofRecord, + }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: proofRequestMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Assert + connectionService.assertConnectionOrServiceDecorator(messageContext) + + // Save in repository + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + } + + return proofRecord + } + + public async negotiateRequest( + agentContext: AgentContext, + { proofFormats, proofRecord, comment, autoAcceptProof }: NegotiateProofRequestOptions<[IndyProofFormatServiceLike]> + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.RequestReceived) + this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support negotiation.` + ) + } + + if (!proofFormats.indy) { + throw new AriesFrameworkError('Missing indy proof format in v1 negotiate request call.') + } + + const presentationProposal = new V1PresentationPreview({ + attributes: proofFormats.indy?.attributes, + predicates: proofFormats.indy?.predicates, + }) + + // validate input data from user + MessageValidator.validateSync(presentationProposal) + + const message = new V1ProposePresentationMessage({ + comment, + presentationProposal, + }) + message.setThread({ threadId: proofRecord.threadId }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) + + return { proofRecord, message: message } + } + + public async acceptRequest( + agentContext: AgentContext, + { proofRecord, proofFormats, autoAcceptProof, comment }: AcceptProofRequestOptions<[IndyProofFormatServiceLike]> + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.RequestReceived) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + const indyProofRequest = requestMessage.indyProofRequest + + if (!requestAttachment || !indyProofRequest) { + throw new V1PresentationProblemReportError( + `Missing indy attachment in request message for presentation with thread id ${proofRecord.threadId}`, + { problemCode: PresentationProblemReportReason.Abandoned } + ) + } + + const proposalAttachment = proposalMessage + ? new Attachment({ + data: { + json: JsonTransformer.toJSON( + createRequestFromPreview({ + attributes: proposalMessage.presentationProposal?.attributes, + predicates: proposalMessage.presentationProposal?.predicates, + name: indyProofRequest.name, + nonce: indyProofRequest.nonce, + version: indyProofRequest.nonce, + }) + ), + }, + }) + : undefined + + const { attachment } = await this.indyProofFormat.acceptRequest(agentContext, { + attachmentId: INDY_PROOF_ATTACHMENT_ID, + requestAttachment, + proposalAttachment, + proofFormats, + proofRecord, + }) + + const message = new V1PresentationMessage({ + comment, + presentationAttachments: [attachment], + }) + message.setThread({ threadId: proofRecord.threadId }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + // Update record + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) + + return { message, proofRecord } + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { proofRecord, proofFormats }: GetCredentialsForRequestOptions<[IndyProofFormatServiceLike]> + ): Promise> { + if (proofFormats) this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + const indyProofRequest = requestMessage.indyProofRequest + + if (!requestAttachment || !indyProofRequest) { + throw new AriesFrameworkError( + `Missing indy attachment in request message for presentation with thread id ${proofRecord.threadId}` + ) + } + + const proposalAttachment = proposalMessage + ? new Attachment({ + data: { + json: JsonTransformer.toJSON( + createRequestFromPreview({ + attributes: proposalMessage.presentationProposal?.attributes, + predicates: proposalMessage.presentationProposal?.predicates, + name: indyProofRequest.name, + nonce: indyProofRequest.nonce, + version: indyProofRequest.nonce, + }) + ), + }, + }) + : undefined + + const credentialForRequest = await this.indyProofFormat.getCredentialsForRequest(agentContext, { + proofRecord, + requestAttachment, + proofFormats, + proposalAttachment, + }) + + return { + proofFormats: { + indy: credentialForRequest, + }, + } + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { proofRecord, proofFormats }: SelectCredentialsForRequestOptions<[IndyProofFormatServiceLike]> + ): Promise> { + if (proofFormats) this.assertOnlyIndyFormat(proofFormats) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + const indyProofRequest = requestMessage.indyProofRequest + + if (!requestAttachment || !indyProofRequest) { + throw new AriesFrameworkError( + `Missing indy attachment in request message for presentation with thread id ${proofRecord.threadId}` + ) + } + + const proposalAttachment = proposalMessage + ? new Attachment({ + data: { + json: JsonTransformer.toJSON( + createRequestFromPreview({ + attributes: proposalMessage.presentationProposal?.attributes, + predicates: proposalMessage.presentationProposal?.predicates, + name: indyProofRequest.name, + nonce: indyProofRequest.nonce, + version: indyProofRequest.nonce, + }) + ), + }, + }) + : undefined + + const selectedCredentials = await this.indyProofFormat.selectCredentialsForRequest(agentContext, { + proofFormats, + proofRecord, + requestAttachment, + proposalAttachment, + }) + + return { + proofFormats: { + indy: selectedCredentials, + }, + } + } + + public async processPresentation( + messageContext: InboundMessageContext + ): Promise { + const { message: presentationMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing presentation with message id ${presentationMessage.id}`) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + const proofRecord = await this.getByThreadAndConnectionId( + agentContext, + presentationMessage.threadId, + connection?.id + ) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1ProposePresentationMessage, + }) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + // Assert + proofRecord.assertState(ProofState.RequestSent) + proofRecord.assertProtocolVersion('v1') + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage: proposalMessage, + previousSentMessage: requestMessage, + }) + + const presentationAttachment = presentationMessage.getPresentationAttachmentById(INDY_PROOF_ATTACHMENT_ID) + if (!presentationAttachment) { + throw new AriesFrameworkError('Missing indy proof attachment in processPresentation') + } + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) { + throw new AriesFrameworkError('Missing indy proof request attachment in processPresentation') + } + + const isValid = await this.indyProofFormat.processPresentation(agentContext, { + proofRecord, + attachment: presentationAttachment, + requestAttachment, + }) + + await didCommMessageRepository.saveAgentMessage(agentContext, { + agentMessage: presentationMessage, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Receiver, + }) + + // Update record + proofRecord.isVerified = isValid + await this.updateState(agentContext, proofRecord, ProofState.PresentationReceived) + + return proofRecord + } + + public async acceptPresentation( + agentContext: AgentContext, + { proofRecord }: AcceptPresentationOptions + ): Promise> { + agentContext.config.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) + + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.PresentationReceived) + + // Create message + const ackMessage = new V1PresentationAckMessage({ + status: AckStatus.OK, + threadId: proofRecord.threadId, + }) + + // Update record + await this.updateState(agentContext, proofRecord, ProofState.Done) + + return { message: ackMessage, proofRecord } + } + + public async processAck( + messageContext: InboundMessageContext + ): Promise { + const { message: presentationAckMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing presentation ack with message id ${presentationAckMessage.id}`) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // TODO: with this method, we should update the credential protocol to use the ConnectionApi, so it + // only depends on the public api, rather than the internal API (this helps with breaking changes) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + const proofRecord = await this.getByThreadAndConnectionId( + agentContext, + presentationAckMessage.threadId, + connection?.id + ) + + const previousReceivedMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const previousSentMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1PresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v1') + proofRecord.assertState(ProofState.PresentationSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + // Update record + await this.updateState(agentContext, proofRecord, ProofState.Done) + + return proofRecord + } + + public async createProblemReport( + agentContext: AgentContext, + { proofRecord, description }: CreateProofProblemReportOptions + ): Promise> { + const message = new V1PresentationProblemReportMessage({ + description: { + code: PresentationProblemReportReason.Abandoned, + en: description, + }, + }) + + message.setThread({ + threadId: proofRecord.threadId, + parentThreadId: proofRecord.parentThreadId, + }) + + return { + proofRecord, + message, + } + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + proposalMessage: V1ProposePresentationMessage + } + ): Promise { + const { proofRecord, proposalMessage } = options + + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + // We are in the ContentApproved case. We need to make sure we've sent a request, and it matches the proposal + const requestMessage = await this.findRequestMessage(agentContext, proofRecord.id) + const requestAttachment = requestMessage?.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) return false + + const rfc0592Proposal = JsonTransformer.toJSON( + createRequestFromPreview({ + name: 'Proof Request', + nonce: await agentContext.wallet.generateNonce(), + version: '1.0', + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + ) + + return this.indyProofFormat.shouldAutoRespondToProposal(agentContext, { + proofRecord, + proposalAttachment: new Attachment({ + data: { + json: rfc0592Proposal, + }, + }), + requestAttachment, + }) + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + requestMessage: V1RequestPresentationMessage + } + ): Promise { + const { proofRecord, requestMessage } = options + + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const requestAttachment = requestMessage.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) return false + + // We are in the ContentApproved case. We need to make sure we've sent a proposal, and it matches the request + const proposalMessage = await this.findProposalMessage(agentContext, proofRecord.id) + if (!proposalMessage) return false + + const rfc0592Proposal = createRequestFromPreview({ + name: 'Proof Request', + nonce: await agentContext.wallet.generateNonce(), + version: '1.0', + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }).toJSON() + + return this.indyProofFormat.shouldAutoRespondToRequest(agentContext, { + proofRecord, + proposalAttachment: new Attachment({ + data: { + base64: JsonEncoder.toBase64(rfc0592Proposal), + }, + }), + requestAttachment, + }) + } + + public async shouldAutoRespondToPresentation( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + presentationMessage: V1PresentationMessage + } + ): Promise { + const { proofRecord, presentationMessage } = options + + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const presentationAttachment = presentationMessage.getPresentationAttachmentById(INDY_PROOF_ATTACHMENT_ID) + if (!presentationAttachment) return false + + // We are in the ContentApproved case. We need to make sure we've sent a request, and it matches the presentation + const requestMessage = await this.findRequestMessage(agentContext, proofRecord.id) + const requestAttachment = requestMessage?.getRequestAttachmentById(INDY_PROOF_REQUEST_ATTACHMENT_ID) + if (!requestAttachment) return false + + // We are in the ContentApproved case. We need to make sure we've sent a proposal, and it matches the request + const proposalMessage = await this.findProposalMessage(agentContext, proofRecord.id) + + const rfc0592Proposal = proposalMessage + ? JsonTransformer.toJSON( + createRequestFromPreview({ + name: 'Proof Request', + nonce: await agentContext.wallet.generateNonce(), + version: '1.0', + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + ) + : undefined + + return this.indyProofFormat.shouldAutoRespondToPresentation(agentContext, { + proofRecord, + requestAttachment, + presentationAttachment, + proposalAttachment: new Attachment({ + data: { + json: rfc0592Proposal, + }, + }), + }) + } + + public async findProposalMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1ProposePresentationMessage, + }) + } + + public async findRequestMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1RequestPresentationMessage, + }) + } + + public async findPresentationMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V1PresentationMessage, + }) + } + + public async getFormatData( + agentContext: AgentContext, + proofRecordId: string + ): Promise> { + // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. + const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ + this.findProposalMessage(agentContext, proofRecordId), + this.findRequestMessage(agentContext, proofRecordId), + this.findPresentationMessage(agentContext, proofRecordId), + ]) + + let indyProposeProof = undefined + const indyRequestProof = requestMessage?.indyProofRequest ?? undefined + const indyPresentProof = presentationMessage?.indyProof ?? undefined + + if (proposalMessage && indyRequestProof) { + indyProposeProof = createRequestFromPreview({ + name: indyRequestProof.name, + version: indyRequestProof.version, + nonce: indyRequestProof.nonce, + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + } else if (proposalMessage) { + indyProposeProof = createRequestFromPreview({ + name: 'Proof Request', + version: '1.0', + nonce: await agentContext.wallet.generateNonce(), + attributes: proposalMessage.presentationProposal.attributes, + predicates: proposalMessage.presentationProposal.predicates, + }) + } + + return { + proposal: proposalMessage + ? { + indy: indyProposeProof?.toJSON(), + } + : undefined, + request: requestMessage + ? { + indy: indyRequestProof, + } + : undefined, + presentation: presentationMessage + ? { + indy: indyPresentProof, + } + : undefined, + } + } + + private assertOnlyIndyFormat(proofFormats: Record) { + const formatKeys = Object.keys(proofFormats) + + // It's fine to not have any formats in some cases, if indy is required the method that calls this should check for this + if (formatKeys.length === 0) return + + if (formatKeys.length !== 1 || !formatKeys.includes('indy')) { + throw new AriesFrameworkError('Only indy proof format is supported for present proof v1 protocol') + } + } +} diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts b/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts deleted file mode 100644 index 22b43cdc93..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofService.ts +++ /dev/null @@ -1,1111 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { AgentMessage } from '../../../../agent/AgentMessage' -import type { Dispatcher } from '../../../../agent/Dispatcher' -import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import type { Attachment } from '../../../../decorators/attachment/Attachment' -import type { MediationRecipientService } from '../../../routing/services/MediationRecipientService' -import type { RoutingService } from '../../../routing/services/RoutingService' -import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' -import type { ProofFormat } from '../../formats/ProofFormat' -import type { IndyProofFormat, IndyProposeProofFormat } from '../../formats/indy/IndyProofFormat' -import type { ProofAttributeInfo } from '../../formats/indy/models' -import type { - CreateProblemReportOptions, - FormatCreatePresentationOptions, -} from '../../formats/models/ProofFormatServiceOptions' -import type { - CreateAckOptions, - CreatePresentationOptions, - CreateProofRequestFromProposalOptions, - CreateProposalAsResponseOptions, - CreateProposalOptions, - CreateRequestAsResponseOptions, - CreateRequestOptions, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, - GetFormatDataReturn, - GetRequestedCredentialsForProofRequestOptions, - ProofRequestFromProposalOptions, -} from '../../models/ProofServiceOptions' - -import { validateOrReject } from 'class-validator' -import { inject, Lifecycle, scoped } from 'tsyringe' - -import { AgentConfig } from '../../../../agent/AgentConfig' -import { EventEmitter } from '../../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../../constants' -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { DidCommMessageRole } from '../../../../storage' -import { DidCommMessageRepository } from '../../../../storage/didcomm/DidCommMessageRepository' -import { checkProofRequestForDuplicates } from '../../../../utils' -import { JsonTransformer } from '../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { Wallet } from '../../../../wallet' -import { AckStatus } from '../../../common/messages/AckMessage' -import { ConnectionService } from '../../../connections' -import { CredentialRepository } from '../../../credentials' -import { IndyCredentialInfo } from '../../../credentials/formats/indy/models/IndyCredentialInfo' -import { IndyHolderService, IndyRevocationService } from '../../../indy' -import { IndyLedgerService } from '../../../ledger/services/IndyLedgerService' -import { ProofService } from '../../ProofService' -import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' -import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' -import { ProofRequest } from '../../formats/indy/models/ProofRequest' -import { RequestedCredentials } from '../../formats/indy/models/RequestedCredentials' -import { ProofState } from '../../models/ProofState' -import { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' -import { ProofRepository } from '../../repository/ProofRepository' - -import { V1PresentationProblemReportError } from './errors' -import { - V1PresentationAckHandler, - V1PresentationHandler, - V1PresentationProblemReportHandler, - V1ProposePresentationHandler, - V1RequestPresentationHandler, -} from './handlers' -import { - INDY_PROOF_ATTACHMENT_ID, - INDY_PROOF_REQUEST_ATTACHMENT_ID, - V1PresentationAckMessage, - V1PresentationMessage, - V1ProposePresentationMessage, - V1RequestPresentationMessage, -} from './messages' -import { V1PresentationProblemReportMessage } from './messages/V1PresentationProblemReportMessage' -import { PresentationPreview } from './models/V1PresentationPreview' - -/** - * @todo add method to check if request matches proposal. Useful to see if a request I received is the same as the proposal I sent. - * @todo add method to reject / revoke messages - * @todo validate attachments / messages - */ -@scoped(Lifecycle.ContainerScoped) -export class V1ProofService extends ProofService<[IndyProofFormat]> { - private credentialRepository: CredentialRepository - private ledgerService: IndyLedgerService - private indyHolderService: IndyHolderService - private indyRevocationService: IndyRevocationService - private indyProofFormatService: IndyProofFormatService - - public constructor( - proofRepository: ProofRepository, - didCommMessageRepository: DidCommMessageRepository, - ledgerService: IndyLedgerService, - @inject(InjectionSymbols.Wallet) wallet: Wallet, - agentConfig: AgentConfig, - connectionService: ConnectionService, - eventEmitter: EventEmitter, - credentialRepository: CredentialRepository, - formatService: IndyProofFormatService, - indyHolderService: IndyHolderService, - indyRevocationService: IndyRevocationService - ) { - super(agentConfig, proofRepository, connectionService, didCommMessageRepository, wallet, eventEmitter) - this.credentialRepository = credentialRepository - this.ledgerService = ledgerService - this.wallet = wallet - this.indyProofFormatService = formatService - this.indyHolderService = indyHolderService - this.indyRevocationService = indyRevocationService - } - - /** - * The version of the present proof protocol this service supports - */ - public readonly version = 'v1' as const - - public async createProposal( - agentContext: AgentContext, - options: CreateProposalOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - const { connectionRecord, proofFormats } = options - - // Assert - connectionRecord.assertReady() - - if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - const presentationProposal = new PresentationPreview({ - attributes: proofFormats.indy?.attributes, - predicates: proofFormats.indy?.predicates, - }) - - // Create message - const proposalMessage = new V1ProposePresentationMessage({ - comment: options?.comment, - presentationProposal, - parentThreadId: options.parentThreadId, - }) - - // Create record - const proofRecord = new ProofExchangeRecord({ - connectionId: connectionRecord.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalSent, - autoAcceptProof: options?.autoAcceptProof, - protocolVersion: 'v1', - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - await this.proofRepository.save(agentContext, proofRecord) - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { proofRecord, message: proposalMessage } - } - - public async createProposalAsResponse( - agentContext: AgentContext, - options: CreateProposalAsResponseOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - const { proofRecord, proofFormats, comment } = options - - // Assert - proofRecord.assertState(ProofState.RequestReceived) - - if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - // Create message - const presentationPreview = new PresentationPreview({ - attributes: proofFormats.indy?.attributes, - predicates: proofFormats.indy?.predicates, - }) - - const proposalMessage: V1ProposePresentationMessage = new V1ProposePresentationMessage({ - comment, - presentationProposal: presentationPreview, - }) - - proposalMessage.setThread({ threadId: proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - // Update record - await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) - - return { proofRecord, message: proposalMessage } - } - - public async processProposal( - messageContext: InboundMessageContext - ): Promise { - let proofRecord: ProofExchangeRecord - const { message: proposalMessage, connection } = messageContext - - this.logger.debug(`Processing presentation proposal with id ${proposalMessage.id}`) - - try { - // Proof record already exists - proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - proposalMessage.threadId, - connection?.id - ) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalMessage, - previousSentMessage: requestMessage ?? undefined, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofExchangeRecord({ - connectionId: connection?.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v1', - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save record - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - await this.proofRepository.save(messageContext.agentContext, proofRecord) - - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - public async createRequestAsResponse( - agentContext: AgentContext, - options: CreateRequestAsResponseOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - const { proofRecord, comment, proofFormats } = options - if (!proofFormats.indy) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - // Assert - proofRecord.assertState(ProofState.ProposalReceived) - - // Create message - const { attachment } = await this.indyProofFormatService.createRequest({ - id: INDY_PROOF_REQUEST_ATTACHMENT_ID, - formats: proofFormats, - }) - - const requestPresentationMessage = new V1RequestPresentationMessage({ - comment, - requestPresentationAttachments: [attachment], - }) - requestPresentationMessage.setThread({ - threadId: proofRecord.threadId, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: requestPresentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - // Update record - await this.updateState(agentContext, proofRecord, ProofState.RequestSent) - - return { message: requestPresentationMessage, proofRecord } - } - - public async createRequest( - agentContext: AgentContext, - options: CreateRequestOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - this.logger.debug(`Creating proof request`) - - // Assert - if (options.connectionRecord) { - options.connectionRecord.assertReady() - } - - if (!options.proofFormats.indy || Object.keys(options.proofFormats).length !== 1) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - // Create message - const { attachment } = await this.indyProofFormatService.createRequest({ - id: INDY_PROOF_REQUEST_ATTACHMENT_ID, - formats: options.proofFormats, - }) - - const requestPresentationMessage = new V1RequestPresentationMessage({ - comment: options?.comment, - requestPresentationAttachments: [attachment], - parentThreadId: options.parentThreadId, - }) - - // Create record - const proofRecord = new ProofExchangeRecord({ - connectionId: options.connectionRecord?.id, - threadId: requestPresentationMessage.threadId, - parentThreadId: requestPresentationMessage.thread?.parentThreadId, - state: ProofState.RequestSent, - autoAcceptProof: options?.autoAcceptProof, - protocolVersion: 'v1', - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: requestPresentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - await this.proofRepository.save(agentContext, proofRecord) - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { message: requestPresentationMessage, proofRecord } - } - - public async processRequest( - messageContext: InboundMessageContext - ): Promise { - let proofRecord: ProofExchangeRecord - const { message: proofRequestMessage, connection } = messageContext - - this.logger.debug(`Processing presentation request with id ${proofRequestMessage.id}`) - - const requestAttachments = proofRequestMessage.getAttachmentFormats() - - for (const attachmentFormat of requestAttachments) { - await this.indyProofFormatService.processRequest({ - requestAttachment: attachmentFormat, - }) - } - - const proofRequest = proofRequestMessage.indyProofRequest - - // Assert attachment - if (!proofRequest) { - throw new V1PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${proofRequestMessage.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - await validateOrReject(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - this.logger.debug('received proof request', proofRequest) - - try { - // Proof record already exists - proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - proofRequestMessage.threadId, - connection?.id - ) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.ProposalSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: requestMessage ?? undefined, - previousSentMessage: proposalMessage ?? undefined, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proofRequestMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofExchangeRecord({ - connectionId: connection?.id, - threadId: proofRequestMessage.threadId, - parentThreadId: proofRequestMessage.thread?.parentThreadId, - state: ProofState.RequestReceived, - protocolVersion: 'v1', - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proofRequestMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save in repository - await this.proofRepository.save(messageContext.agentContext, proofRecord) - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - public async createPresentation( - agentContext: AgentContext, - options: CreatePresentationOptions<[IndyProofFormat]> - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - const { proofRecord, proofFormats } = options - - this.logger.debug(`Creating presentation for proof record with id ${proofRecord.id}`) - - if (!proofFormats.indy || Object.keys(proofFormats).length !== 1) { - throw new AriesFrameworkError('Only indy proof format is supported for present proof protocol v1') - } - - // Assert - proofRecord.assertState(ProofState.RequestReceived) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const requestAttachment = requestMessage?.indyAttachment - - if (!requestAttachment) { - throw new V1PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation with thread id ${proofRecord.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - - const presentationOptions: FormatCreatePresentationOptions = { - id: INDY_PROOF_ATTACHMENT_ID, - attachment: requestAttachment, - proofFormats: proofFormats, - } - - const proof = await this.indyProofFormatService.createPresentation(agentContext, presentationOptions) - - // Extract proof request from attachment - const proofRequestJson = requestAttachment.getDataAsJson() ?? null - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - const requestedCredentials = new RequestedCredentials({ - requestedAttributes: proofFormats.indy?.requestedAttributes, - requestedPredicates: proofFormats.indy?.requestedPredicates, - selfAttestedAttributes: proofFormats.indy?.selfAttestedAttributes, - }) - - // Get the matching attachments to the requested credentials - const linkedAttachments = await this.getRequestedAttachmentsForRequestedCredentials( - agentContext, - proofRequest, - requestedCredentials - ) - - const presentationMessage = new V1PresentationMessage({ - comment: options?.comment, - presentationAttachments: [proof.attachment], - attachments: linkedAttachments, - }) - presentationMessage.setThread({ threadId: proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: presentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - // Update record - await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) - - return { message: presentationMessage, proofRecord } - } - - public async processPresentation( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationMessage, connection } = messageContext - - this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationMessage.threadId, - connection?.id - ) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalMessage ?? undefined, - previousSentMessage: requestMessage ?? undefined, - }) - - try { - const isValid = await this.indyProofFormatService.processPresentation(messageContext.agentContext, { - record: proofRecord, - formatAttachments: { - presentation: presentationMessage.getAttachmentFormats(), - request: requestMessage.getAttachmentFormats(), - }, - }) - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: presentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - proofRecord.isVerified = isValid - await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) - } catch (e) { - if (e instanceof AriesFrameworkError) { - throw new V1PresentationProblemReportError(e.message, { - problemCode: PresentationProblemReportReason.Abandoned, - }) - } - throw e - } - - return proofRecord - } - - public async processAck( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationAckMessage, connection } = messageContext - - this.logger.debug(`Processing presentation ack with id ${presentationAckMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationAckMessage.threadId, - connection?.id - ) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1PresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.PresentationSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: requestMessage ?? undefined, - previousSentMessage: presentationMessage ?? undefined, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) - - return proofRecord - } - - public async createProblemReport( - agentContext: AgentContext, - options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - const msg = new V1PresentationProblemReportMessage({ - description: { - code: PresentationProblemReportReason.Abandoned, - en: options.description, - }, - }) - - msg.setThread({ - threadId: options.proofRecord.threadId, - parentThreadId: options.proofRecord.parentThreadId, - }) - - return { - proofRecord: options.proofRecord, - message: msg, - } - } - - public async processProblemReport( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationProblemReportMessage } = messageContext - - const connection = messageContext.assertReadyConnection() - - this.logger.debug(`Processing problem report with id ${presentationProblemReportMessage.id}`) - - const proofRecord = await this.getByThreadAndConnectionId( - messageContext.agentContext, - presentationProblemReportMessage.threadId, - connection?.id - ) - - proofRecord.errorMessage = `${presentationProblemReportMessage.description.code}: ${presentationProblemReportMessage.description.en}` - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Abandoned) - return proofRecord - } - - public async createProofRequestFromProposal( - agentContext: AgentContext, - options: CreateProofRequestFromProposalOptions - ): Promise> { - const proofRecordId = options.proofRecord.id - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V1ProposePresentationMessage, - }) - - if (!proposalMessage) { - throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) - } - - const indyProposeProofFormat: IndyProposeProofFormat = { - name: 'Proof Request', - version: '1.0', - nonce: await this.wallet.generateNonce(), - } - - const proofRequest: ProofRequest = await this.indyProofFormatService.createReferentForProofRequest( - indyProposeProofFormat, - proposalMessage.presentationProposal - ) - - return { - proofRecord: options.proofRecord, - proofFormats: { - indy: proofRequest, - }, - } - } - - /** - * Retrieves the linked attachments for an {@link indyProofRequest} - * @param indyProofRequest The proof request for which the linked attachments have to be found - * @param requestedCredentials The requested credentials - * @returns a list of attachments that are linked to the requested credentials - */ - public async getRequestedAttachmentsForRequestedCredentials( - agentContext: AgentContext, - indyProofRequest: ProofRequest, - requestedCredentials: RequestedCredentials - ): Promise { - const attachments: Attachment[] = [] - const credentialIds = new Set() - const requestedAttributesNames: (string | undefined)[] = [] - - // Get the credentialIds if it contains a hashlink - for (const [referent, requestedAttribute] of Object.entries(requestedCredentials.requestedAttributes)) { - // Find the requested Attributes - const requestedAttributes = indyProofRequest.requestedAttributes.get(referent) as ProofAttributeInfo - - // List the requested attributes - requestedAttributesNames.push(...(requestedAttributes.names ?? [requestedAttributes.name])) - - //Get credentialInfo - if (!requestedAttribute.credentialInfo) { - const indyCredentialInfo = await this.indyHolderService.getCredential( - agentContext, - requestedAttribute.credentialId - ) - requestedAttribute.credentialInfo = JsonTransformer.fromJSON(indyCredentialInfo, IndyCredentialInfo) - } - - // Find the attributes that have a hashlink as a value - for (const attribute of Object.values(requestedAttribute.credentialInfo.attributes)) { - if (attribute.toLowerCase().startsWith('hl:')) { - credentialIds.add(requestedAttribute.credentialId) - } - } - } - - // Only continues if there is an attribute value that contains a hashlink - for (const credentialId of credentialIds) { - // Get the credentialRecord that matches the ID - - const credentialRecord = await this.credentialRepository.getSingleByQuery(agentContext, { - credentialIds: [credentialId], - }) - - if (credentialRecord.linkedAttachments) { - // Get the credentials that have a hashlink as value and are requested - const requestedCredentials = credentialRecord.credentialAttributes?.filter( - (credential) => - credential.value.toLowerCase().startsWith('hl:') && requestedAttributesNames.includes(credential.name) - ) - - // Get the linked attachments that match the requestedCredentials - const linkedAttachments = credentialRecord.linkedAttachments.filter((attachment) => - requestedCredentials?.map((credential) => credential.value.split(':')[1]).includes(attachment.id) - ) - - if (linkedAttachments) { - attachments.push(...linkedAttachments) - } - } - } - - return attachments.length ? attachments : undefined - } - - public async shouldAutoRespondToProposal( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - if (!proposal) return false - MessageValidator.validateSync(proposal) - - // check the proposal against a possible previous request - const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - if (!request) return false - - const proofRequest = request.indyProofRequest - - if (!proofRequest) { - throw new V1PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${request.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - await validateOrReject(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - const proposalAttributes = proposal.presentationProposal.attributes - const requestedAttributes = proofRequest.requestedAttributes - - const proposedAttributeNames = proposalAttributes.map((x) => x.name) - let requestedAttributeNames: string[] = [] - - const requestedAttributeList = Array.from(requestedAttributes.values()) - - requestedAttributeList.forEach((x) => { - if (x.name) { - requestedAttributeNames.push(x.name) - } else if (x.names) { - requestedAttributeNames = requestedAttributeNames.concat(x.names) - } - }) - - if (requestedAttributeNames.length > proposedAttributeNames.length) { - // more attributes are requested than have been proposed - return false - } - - requestedAttributeNames.forEach((x) => { - if (!proposedAttributeNames.includes(x)) { - this.logger.debug(`Attribute ${x} was requested but wasn't proposed.`) - return false - } - }) - - // assert that all requested attributes are provided - const providedPredicateNames = proposal.presentationProposal.predicates.map((x) => x.name) - proofRequest.requestedPredicates.forEach((x) => { - if (!providedPredicateNames.includes(x.name)) { - return false - } - }) - return true - } - - public async shouldAutoRespondToRequest( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - if (!proposal) { - return false - } - - const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - if (!request) { - throw new AriesFrameworkError( - `Expected to find a request message for ProofExchangeRecord with id ${proofRecord.id}` - ) - } - - const proofRequest = request.indyProofRequest - - // Assert attachment - if (!proofRequest) { - throw new V1PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${request.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - await validateOrReject(proofRequest) - - // Assert attribute and predicate (group) names do not match - checkProofRequestForDuplicates(proofRequest) - - const proposalAttributes = proposal.presentationProposal.attributes - const requestedAttributes = proofRequest.requestedAttributes - - const proposedAttributeNames = proposalAttributes.map((x) => x.name) - let requestedAttributeNames: string[] = [] - - const requestedAttributeList = Array.from(requestedAttributes.values()) - - requestedAttributeList.forEach((x) => { - if (x.name) { - requestedAttributeNames.push(x.name) - } else if (x.names) { - requestedAttributeNames = requestedAttributeNames.concat(x.names) - } - }) - - if (requestedAttributeNames.length > proposedAttributeNames.length) { - // more attributes are requested than have been proposed - return false - } - - requestedAttributeNames.forEach((x) => { - if (!proposedAttributeNames.includes(x)) { - this.logger.debug(`Attribute ${x} was requested but wasn't proposed.`) - return false - } - }) - - // assert that all requested attributes are provided - const providedPredicateNames = proposal.presentationProposal.predicates.map((x) => x.name) - proofRequest.requestedPredicates.forEach((x) => { - if (!providedPredicateNames.includes(x.name)) { - return false - } - }) - - return true - } - - public async shouldAutoRespondToPresentation( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - this.logger.debug(`Should auto respond to presentation for proof record id: ${proofRecord.id}`) - return true - } - - public async getRequestedCredentialsForProofRequest( - agentContext: AgentContext, - options: GetRequestedCredentialsForProofRequestOptions - ): Promise> { - const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - const indyProofRequest = requestMessage?.requestPresentationAttachments - - if (!indyProofRequest) { - throw new AriesFrameworkError('Could not find proof request') - } - - const requestedCredentials: FormatRetrievedCredentialOptions<[IndyProofFormat]> = - await this.indyProofFormatService.getRequestedCredentialsForProofRequest(agentContext, { - attachment: indyProofRequest[0], - presentationProposal: proposalMessage?.presentationProposal, - config: options.config ?? undefined, - }) - return requestedCredentials - } - - public async autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions - ): Promise> { - return await this.indyProofFormatService.autoSelectCredentialsForProofRequest(options) - } - - public registerMessageHandlers( - dispatcher: Dispatcher, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - routingService: RoutingService - ): void { - dispatcher.registerMessageHandler( - new V1ProposePresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) - ) - - dispatcher.registerMessageHandler( - new V1RequestPresentationHandler( - this, - agentConfig, - proofResponseCoordinator, - mediationRecipientService, - this.didCommMessageRepository, - routingService - ) - ) - - dispatcher.registerMessageHandler( - new V1PresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) - ) - dispatcher.registerMessageHandler(new V1PresentationAckHandler(this)) - dispatcher.registerMessageHandler(new V1PresentationProblemReportHandler(this)) - } - - public async findRequestMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V1RequestPresentationMessage, - }) - } - public async findPresentationMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V1PresentationMessage, - }) - } - - public async findProposalMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V1ProposePresentationMessage, - }) - } - - public async getFormatData( - agentContext: AgentContext, - proofRecordId: string - ): Promise> { - const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ - this.findProposalMessage(agentContext, proofRecordId), - this.findRequestMessage(agentContext, proofRecordId), - this.findPresentationMessage(agentContext, proofRecordId), - ]) - - const indyProposeProof = proposalMessage - ? JsonTransformer.toJSON(await this.rfc0592ProposalFromV1ProposeMessage(proposalMessage)) - : undefined - const indyRequestProof = requestMessage?.indyProofRequestJson ?? undefined - const indyPresentProof = presentationMessage?.indyProof ?? undefined - - return { - proposal: proposalMessage - ? { - indy: indyProposeProof, - } - : undefined, - request: requestMessage - ? { - indy: indyRequestProof, - } - : undefined, - presentation: presentationMessage - ? { - indy: indyPresentProof, - } - : undefined, - } - } - - private async rfc0592ProposalFromV1ProposeMessage( - proposalMessage: V1ProposePresentationMessage - ): Promise { - const indyFormat: IndyProposeProofFormat = { - name: 'Proof Request', - version: '1.0', - nonce: await this.wallet.generateNonce(), - attributes: proposalMessage.presentationProposal.attributes, - predicates: proposalMessage.presentationProposal.predicates, - } - - if (!indyFormat) { - throw new AriesFrameworkError('No Indy format found.') - } - - const preview = new PresentationPreview({ - attributes: indyFormat.attributes, - predicates: indyFormat.predicates, - }) - - return this.indyProofFormatService.createReferentForProofRequest(indyFormat, preview) - } - /** - * Retrieve all proof records - * - * @returns List containing all proof records - */ - public async getAll(agentContext: AgentContext): Promise { - return this.proofRepository.getAll(agentContext) - } - - /** - * Retrieve a proof record by connection id and thread id - * - * @param connectionId The connection id - * @param threadId The thread id - * @throws {RecordNotFoundError} If no record is found - * @throws {RecordDuplicateError} If multiple records are found - * @returns The proof record - */ - public async getByThreadAndConnectionId( - agentContext: AgentContext, - threadId: string, - connectionId?: string - ): Promise { - return this.proofRepository.getSingleByQuery(agentContext, { threadId, connectionId }) - } - - public async createAck( - gentContext: AgentContext, - options: CreateAckOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - const { proofRecord } = options - this.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) - - // Assert - proofRecord.assertState(ProofState.PresentationReceived) - - // Create message - const ackMessage = new V1PresentationAckMessage({ - status: AckStatus.OK, - threadId: proofRecord.threadId, - }) - - // Update record - await this.updateState(gentContext, proofRecord, ProofState.Done) - - return { message: ackMessage, proofRecord } - } -} diff --git a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/V1ProofProtocol.test.ts similarity index 63% rename from packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts rename to packages/core/src/modules/proofs/protocol/v1/__tests__/V1ProofProtocol.test.ts index 3da57e27c1..0b1febb680 100644 --- a/packages/core/src/modules/proofs/__tests__/V1ProofService.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/V1ProofProtocol.test.ts @@ -1,51 +1,43 @@ -import type { CustomProofTags } from './../repository/ProofExchangeRecord' -import type { AgentContext } from '../../../agent' -import type { Wallet } from '../../../wallet/Wallet' -import type { CredentialRepository } from '../../credentials/repository' -import type { ProofStateChangedEvent } from '../ProofEvents' +import type { AgentContext } from '../../../../../agent' +import type { AgentConfig } from '../../../../../agent/AgentConfig' +import type { ProofStateChangedEvent } from '../../../ProofEvents' +import type { CustomProofTags } from '../../../repository/ProofExchangeRecord' import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../tests/helpers' -import { EventEmitter } from '../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' -import { DidCommMessageRepository } from '../../../storage' -import { ConnectionService, DidExchangeState } from '../../connections' -import { IndyHolderService } from '../../indy/services/IndyHolderService' -import { IndyRevocationService } from '../../indy/services/IndyRevocationService' -import { IndyLedgerService } from '../../ledger/services' -import { ProofEventTypes } from '../ProofEvents' -import { PresentationProblemReportReason } from '../errors/PresentationProblemReportReason' -import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' -import { ProofState } from '../models/ProofState' -import { V1ProofService } from '../protocol/v1' -import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../protocol/v1/messages' -import { V1PresentationProblemReportMessage } from '../protocol/v1/messages/V1PresentationProblemReportMessage' -import { ProofExchangeRecord } from '../repository/ProofExchangeRecord' -import { ProofRepository } from '../repository/ProofRepository' - -import { credDef } from './fixtures' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' +import { EventEmitter } from '../../../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' +import { DidCommMessageRepository } from '../../../../../storage' +import { ConnectionService, DidExchangeState } from '../../../../connections' +import { ProofEventTypes } from '../../../ProofEvents' +import { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' +import { IndyProofFormatService } from '../../../formats/indy/IndyProofFormatService' +import { ProofState } from '../../../models/ProofState' +import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' +import { ProofRepository } from '../../../repository/ProofRepository' +import { V1ProofProtocol } from '../V1ProofProtocol' +import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../messages' +import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' // Mock classes -jest.mock('../repository/ProofRepository') -jest.mock('../../../modules/ledger/services/IndyLedgerService') -jest.mock('../../indy/services/IndyHolderService') -jest.mock('../../indy/services/IndyIssuerService') -jest.mock('../../indy/services/IndyVerifierService') -jest.mock('../../indy/services/IndyRevocationService') -jest.mock('../../connections/services/ConnectionService') -jest.mock('../../../storage/Repository') +jest.mock('../../../repository/ProofRepository') +jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') +jest.mock('../../../../connections/services/ConnectionService') +jest.mock('../../../../../storage/Repository') // Mock typed object const ProofRepositoryMock = ProofRepository as jest.Mock -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const IndyHolderServiceMock = IndyHolderService as jest.Mock -const IndyRevocationServiceMock = IndyRevocationService as jest.Mock const connectionServiceMock = ConnectionService as jest.Mock const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const indyProofFormatServiceMock = IndyProofFormatService as jest.Mock +const proofRepository = new ProofRepositoryMock() +const connectionService = new connectionServiceMock() +const didCommMessageRepository = new didCommMessageRepositoryMock() +const indyProofFormatService = new indyProofFormatServiceMock() + const connection = getMockConnection({ id: '123', state: DidExchangeState.Completed, @@ -78,7 +70,7 @@ const mockProofExchangeRecord = ({ } = {}) => { const requestPresentationMessage = new V1RequestPresentationMessage({ comment: 'some comment', - requestPresentationAttachments: [requestAttachment], + requestAttachments: [requestAttachment], }) const proofRecord = new ProofExchangeRecord({ @@ -93,58 +85,37 @@ const mockProofExchangeRecord = ({ return proofRecord } -describe('V1ProofService', () => { - let proofRepository: ProofRepository - let proofService: V1ProofService - let ledgerService: IndyLedgerService - let wallet: Wallet - let indyHolderService: IndyHolderService - let indyRevocationService: IndyRevocationService +describe('V1ProofProtocol', () => { let eventEmitter: EventEmitter - let credentialRepository: CredentialRepository - let connectionService: ConnectionService - let didCommMessageRepository: DidCommMessageRepository - let indyProofFormatService: IndyProofFormatService + let agentConfig: AgentConfig let agentContext: AgentContext + let proofProtocol: V1ProofProtocol beforeEach(() => { - const agentConfig = getAgentConfig('V1ProofServiceTest') - agentContext = getAgentContext() - proofRepository = new ProofRepositoryMock() - indyHolderService = new IndyHolderServiceMock() - indyRevocationService = new IndyRevocationServiceMock() - ledgerService = new IndyLedgerServiceMock() + // real objects + agentConfig = getAgentConfig('V1ProofProtocolTest') eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - connectionService = new connectionServiceMock() - didCommMessageRepository = new didCommMessageRepositoryMock() - indyProofFormatService = new indyProofFormatServiceMock() - agentContext = getAgentContext() - - proofService = new V1ProofService( - proofRepository, - didCommMessageRepository, - ledgerService, - wallet, + + agentContext = getAgentContext({ + registerInstances: [ + [ProofRepository, proofRepository], + [DidCommMessageRepository, didCommMessageRepository], + [EventEmitter, eventEmitter], + [ConnectionService, connectionService], + ], agentConfig, - connectionService, - eventEmitter, - credentialRepository, - indyProofFormatService, - indyHolderService, - indyRevocationService - ) - - mockFunction(ledgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) + }) + proofProtocol = new V1ProofProtocol({ indyProofFormat: indyProofFormatService }) }) - describe('processProofRequest', () => { + describe('processRequest', () => { let presentationRequest: V1RequestPresentationMessage let messageContext: InboundMessageContext beforeEach(() => { presentationRequest = new V1RequestPresentationMessage({ comment: 'abcd', - requestPresentationAttachments: [requestAttachment], + requestAttachments: [requestAttachment], }) messageContext = new InboundMessageContext(presentationRequest, { connection, @@ -156,7 +127,7 @@ describe('V1ProofService', () => { const repositorySaveSpy = jest.spyOn(proofRepository, 'save') // when - const returnedProofExchangeRecord = await proofService.processRequest(messageContext) + const returnedProofExchangeRecord = await proofProtocol.processRequest(messageContext) // then const expectedProofExchangeRecord = { @@ -178,7 +149,7 @@ describe('V1ProofService', () => { eventEmitter.on(ProofEventTypes.ProofStateChanged, eventListenerMock) // when - await proofService.processRequest(messageContext) + await proofProtocol.processRequest(messageContext) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -261,7 +232,7 @@ describe('V1ProofService', () => { mockFunction(proofRepository.getSingleByQuery).mockReturnValue(Promise.resolve(proof)) // when - const returnedCredentialRecord = await proofService.processProblemReport(messageContext) + const returnedCredentialRecord = await proofProtocol.processProblemReport(messageContext) // then const expectedCredentialRecord = { diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts index ee7b481cbb..e7f7a6f5f1 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts @@ -1,8 +1,8 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions, NegotiateProposalOptions } from '../../../ProofsApiOptions' +import type { AcceptProofProposalOptions, NegotiateProofProposalOptions } from '../../../ProofsApiOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../models/V1PresentationPreview' +import type { V1PresentationPreview } from '../models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' @@ -20,7 +20,7 @@ describe('Present Proof', () => { let aliceAgent: Agent let credDefId: CredDefId let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository @@ -54,7 +54,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', attributes: presentationPreview.attributes.filter((attribute) => attribute.name !== 'name'), predicates: presentationPreview.predicates, @@ -135,7 +134,7 @@ describe('Present Proof', () => { }), } - const requestProofAsResponseOptions: NegotiateProposalOptions = { + const requestProofAsResponseOptions: NegotiateProofProposalOptions = { proofRecordId: faberProofExchangeRecord.id, proofFormats: { indy: { @@ -169,7 +168,7 @@ describe('Present Proof', () => { expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -200,7 +199,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', attributes: presentationPreview.attributes.filter((attribute) => attribute.name === 'name'), predicates: presentationPreview.predicates, @@ -276,7 +274,7 @@ describe('Present Proof', () => { expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -296,9 +294,8 @@ describe('Present Proof', () => { protocolVersion: 'v1', }) - const presentationProposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) - - expect(presentationProposalMessage).toMatchObject({ + const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) + expect(proposalMessage).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), comment: 'V1 propose proof test 2', @@ -327,29 +324,31 @@ describe('Present Proof', () => { aliceProofExchangeRecord.id )) as V1RequestPresentationMessage - const predicateKey = proofRequestMessage.indyProofRequest?.requestedPredicates?.keys().next().value - const predicate = Object.values(predicates)[0] - + const predicateKey = Object.keys(proofRequestMessage.indyProofRequest?.requested_predicates ?? {})[0] expect(proofRequestMessage.indyProofRequest).toMatchObject({ name: 'Proof Request', version: '1.0', - requestedAttributes: new Map( - Object.entries({ - '0': new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - }) - ), - requestedPredicates: new Map( - Object.entries({ - [predicateKey]: predicate, - }) - ), + requested_attributes: { + '0': { + name: 'name', + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + requested_predicates: { + [predicateKey]: { + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, }) }) }) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.e2e.ts similarity index 93% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts rename to packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.e2e.ts index 8b32bbe14c..86095b8f01 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.e2e.ts @@ -1,19 +1,19 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { PresentationPreview } from '../models/V1PresentationPreview' +import type { V1PresentationPreview } from '../models/V1PresentationPreview' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage/didcomm' import { ProofState } from '../../../models/ProofState' -import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' +import { ProofExchangeRecord } from '../../../repository' import { V1PresentationMessage, V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository @@ -21,8 +21,8 @@ describe('Present Proof', () => { beforeAll(async () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' + 'Faber Agent Proofs', + 'Alice Agent Proofs' )) }) @@ -47,7 +47,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, @@ -128,7 +127,7 @@ describe('Present Proof', () => { expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -150,11 +149,8 @@ describe('Present Proof', () => { }) test(`Alice accepts presentation request from Faber`, async () => { - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -217,7 +213,7 @@ describe('Present Proof', () => { }) // Faber accepts the presentation provided by Alice - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.e2e.ts similarity index 95% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts rename to packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.e2e.ts index 606f1e7ff8..f69048fece 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.e2e.ts @@ -1,7 +1,7 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../models/V1PresentationPreview' +import type { V1PresentationPreview } from '../models/V1PresentationPreview' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' @@ -13,7 +13,7 @@ describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview let faberProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository @@ -46,7 +46,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, diff --git a/packages/core/tests/v1-connectionless-proofs.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts similarity index 89% rename from packages/core/tests/v1-connectionless-proofs.test.ts rename to packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts index d2fe8af3c3..fcfaaaebf1 100644 --- a/packages/core/tests/v1-connectionless-proofs.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -1,36 +1,29 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { ProofStateChangedEvent } from '../src/modules/proofs' +import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { ProofStateChangedEvent } from '../../../ProofEvents' import { Subject, ReplaySubject } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { Agent } from '../src/agent/Agent' -import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' -import { HandshakeProtocol } from '../src/modules/connections' -import { V1CredentialPreview } from '../src/modules/credentials' +import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' import { - PredicateType, - ProofState, - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - AutoAcceptProof, - ProofEventTypes, -} from '../src/modules/proofs' -import { MediatorPickupStrategy } from '../src/modules/routing' -import { LinkedAttachment } from '../src/utils/LinkedAttachment' -import { uuid } from '../src/utils/uuid' - -import { - getAgentOptions, - issueCredential, - makeConnection, - prepareForIssuance, setupProofsTest, waitForProofExchangeRecordSubject, -} from './helpers' -import testLogger from './logger' + getAgentOptions, + prepareForIssuance, + makeConnection, + issueCredential, +} from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { Agent } from '../../../../../agent/Agent' +import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' +import { LinkedAttachment } from '../../../../../utils/LinkedAttachment' +import { uuid } from '../../../../../utils/uuid' +import { HandshakeProtocol } from '../../../../connections' +import { V1CredentialPreview } from '../../../../credentials' +import { MediatorPickupStrategy } from '../../../../routing' +import { ProofEventTypes } from '../../../ProofEvents' +import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' +import { AutoAcceptProof, ProofState } from '../../../models' describe('Present Proof', () => { let agents: Agent[] @@ -86,7 +79,6 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - nonce: '12345678901', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -104,11 +96,8 @@ describe('Present Proof', () => { let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { @@ -133,7 +122,7 @@ describe('Present Proof', () => { }) // Faber accepts presentation - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits till it receives presentation ack aliceProofExchangeRecord = await aliceProofExchangeRecordPromise @@ -189,7 +178,6 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - nonce: '12345678901', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -353,7 +341,6 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - nonce: '12345678901', requestedAttributes: attributes, requestedPredicates: predicates, }, diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proof-request.e2e.test.ts similarity index 70% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts rename to packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proof-request.e2e.test.ts index c3bccd9f9b..8c9278b879 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-request.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proof-request.e2e.test.ts @@ -1,23 +1,19 @@ import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../models/V1PresentationPreview' +import type { ConnectionRecord } from '../../../../connections' +import type { ProofExchangeRecord } from '../../../repository' +import type { V1PresentationPreview } from '../models' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { ProofState } from '../../../models/ProofState' -import { V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' +import { ProofState } from '../../../models' -describe('Present Proof', () => { +describe('Present Proof | V1ProofProtocol', () => { let faberAgent: Agent let aliceAgent: Agent let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { testLogger.test('Initializing the agents') @@ -47,8 +43,7 @@ describe('Present Proof', () => { protocolVersion: 'v1', proofFormats: { indy: { - name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + name: 'Proof Request', version: '1.0', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, @@ -60,13 +55,7 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) - + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), @@ -103,34 +92,26 @@ describe('Present Proof', () => { }) }) - test(`Faber accepts the Proposal send by Alice and Creates Proof Request`, async () => { - // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - + test(`Faber accepts the Proposal sent by Alice and Creates Proof Request`, async () => { const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, }) + // Accept Proposal testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) testLogger.test('Alice waits for proof request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -143,6 +124,7 @@ describe('Present Proof', () => { threadId: faberProofExchangeRecord.threadId, }, }) + expect(aliceProofExchangeRecord).toMatchObject({ id: expect.anything(), threadId: faberProofExchangeRecord.threadId, diff --git a/packages/core/tests/v1-indy-proofs.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proofs.e2e.test.ts similarity index 81% rename from packages/core/tests/v1-indy-proofs.test.ts rename to packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proofs.e2e.test.ts index 440da6a5d4..918673b0b3 100644 --- a/packages/core/tests/v1-indy-proofs.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proofs.e2e.test.ts @@ -1,42 +1,29 @@ -import type { Agent, ConnectionRecord } from '../src' -import type { AcceptProofProposalOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' - -import { ProofExchangeRecord } from '../src' -import { getGroupKeysFromIndyProofFormatData } from '../src/modules/proofs/__tests__/groupKeys' -import { - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - PredicateType, -} from '../src/modules/proofs/formats/indy/models' -import { ProofState } from '../src/modules/proofs/models/ProofState' -import { - V1ProposePresentationMessage, - V1RequestPresentationMessage, - V1PresentationMessage, -} from '../src/modules/proofs/protocol/v1/messages' -import { DidCommMessageRepository } from '../src/storage/didcomm' - -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' -import testLogger from './logger' +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections' +import type { V1PresentationPreview } from '../models' + +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { getGroupKeysFromIndyProofFormatData } from '../../../formats/indy/__tests__/groupKeys' +import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' +import { ProofState } from '../../../models' +import { ProofExchangeRecord } from '../../../repository' +import { V1ProposePresentationMessage, V1RequestPresentationMessage, V1PresentationMessage } from '../messages' describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent - let credDefId: CredDefId + let credDefId: string let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: PresentationPreview - let didCommMessageRepository: DidCommMessageRepository + let presentationPreview: V1PresentationPreview beforeAll(async () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent', 'Alice agent')) + await setupProofsTest('Faber agent v1', 'Alice agent v1')) testLogger.test('Issuing second credential') }) @@ -70,14 +57,7 @@ describe('Present Proof', () => { // Faber waits for a presentation proposal from Alice testLogger.test('Faber waits for a presentation proposal from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) - + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), @@ -112,10 +92,6 @@ describe('Present Proof', () => { protocolVersion: 'v1', }) - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { threadId: aliceProofExchangeRecord.threadId, state: ProofState.RequestReceived, @@ -123,21 +99,19 @@ describe('Present Proof', () => { // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -154,11 +128,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -175,11 +146,7 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/presentation', id: expect.any(String), @@ -192,15 +159,15 @@ describe('Present Proof', () => { }, }, ], - appendedAttachments: [ - { - id: expect.any(String), - filename: expect.any(String), - data: { - base64: expect.any(String), - }, - }, - ], + // appendedAttachments: [ + // { + // id: expect.any(String), + // filename: expect.any(String), + // data: { + // base64: expect.any(String), + // }, + // }, + // ], thread: { threadId: expect.any(String), }, @@ -220,7 +187,7 @@ describe('Present Proof', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') @@ -390,17 +357,11 @@ describe('Present Proof', () => { testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -421,11 +382,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -442,11 +400,7 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/presentation', id: expect.any(String), @@ -459,15 +413,15 @@ describe('Present Proof', () => { }, }, ], - appendedAttachments: [ - { - id: expect.any(String), - filename: expect.any(String), - data: { - base64: expect.any(String), - }, - }, - ], + // appendedAttachments: [ + // { + // id: expect.any(String), + // filename: expect.any(String), + // data: { + // base64: expect.any(String), + // }, + // }, + // ], thread: { threadId: expect.any(String), }, @@ -487,7 +441,7 @@ describe('Present Proof', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') @@ -548,7 +502,6 @@ describe('Present Proof', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -604,7 +557,6 @@ describe('Present Proof', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -615,17 +567,11 @@ describe('Present Proof', () => { testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), - requestPresentationAttachments: [ + requestAttachments: [ { id: 'libindy-request-presentation-0', mimeType: 'application/json', @@ -648,10 +594,10 @@ describe('Present Proof', () => { state: ProofState.Abandoned, }) - aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport( - aliceProofExchangeRecord.id, - 'Problem inside proof request' - ) + aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport({ + proofRecordId: aliceProofExchangeRecord.id, + description: 'Problem inside proof request', + }) faberProofExchangeRecord = await faberProofExchangeRecordPromise diff --git a/packages/core/tests/v1-proofs-auto-accept.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts similarity index 82% rename from packages/core/tests/v1-proofs-auto-accept.test.ts rename to packages/core/src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts index 6c222e70e9..c8a116e8ed 100644 --- a/packages/core/tests/v1-proofs-auto-accept.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts @@ -1,17 +1,11 @@ -import type { Agent, ConnectionRecord } from '../src' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections' +import type { V1PresentationPreview } from '../models' -import { - AutoAcceptProof, - ProofState, - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - PredicateType, -} from '../src' - -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' -import testLogger from './logger' +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' +import { AutoAcceptProof, ProofState } from '../../../models' describe('Auto accept present proof', () => { let faberAgent: Agent @@ -19,9 +13,9 @@ describe('Auto accept present proof', () => { let credDefId: string let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview - describe('Auto accept on `always`', () => { + describe("Auto accept on 'always'", () => { beforeAll(async () => { ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = await setupProofsTest( @@ -37,7 +31,7 @@ describe('Auto accept present proof', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { + test("Alice starts with proof proposal to Faber, both with autoAcceptProof on 'always'", async () => { testLogger.test('Alice sends presentation proposal to Faber') await aliceAgent.proofs.proposeProof({ @@ -45,7 +39,6 @@ describe('Auto accept present proof', () => { protocolVersion: 'v1', proofFormats: { indy: { - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', name: 'abc', version: '1.0', attributes: presentationPreview.attributes, @@ -62,7 +55,7 @@ describe('Auto accept present proof', () => { ]) }) - test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'always'", async () => { testLogger.test('Faber sends presentation request to Alice') const attributes = { name: new ProofAttributeInfo({ @@ -94,7 +87,6 @@ describe('Auto accept present proof', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -109,7 +101,7 @@ describe('Auto accept present proof', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + describe("Auto accept on 'contentApproved'", () => { beforeAll(async () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = @@ -127,7 +119,7 @@ describe('Auto accept present proof', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with proof proposal to Faber, both with autoacceptproof on `contentApproved`', async () => { + test("Alice starts with proof proposal to Faber, both with autoAcceptProof on 'contentApproved'", async () => { testLogger.test('Alice sends presentation proposal to Faber') const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ @@ -135,7 +127,6 @@ describe('Auto accept present proof', () => { protocolVersion: 'v1', proofFormats: { indy: { - nonce: '1298236324864', name: 'abc', version: '1.0', attributes: presentationPreview.attributes, @@ -159,7 +150,7 @@ describe('Auto accept present proof', () => { ]) }) - test('Faber starts with proof requests to Alice, both with autoacceptproof on `contentApproved`', async () => { + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'contentApproved'", async () => { testLogger.test('Faber sends presentation request to Alice') const attributes = { name: new ProofAttributeInfo({ @@ -191,7 +182,6 @@ describe('Auto accept present proof', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324866', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -203,7 +193,7 @@ describe('Auto accept present proof', () => { state: ProofState.RequestReceived, }) - const { proofFormats } = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ proofRecordId }) + const { proofFormats } = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId }) await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) await Promise.all([ diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts index cdc9f6d797..c8f331c3a8 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts @@ -1,17 +1,17 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V1ProofService } from '../V1ProofService' +import type { V1ProofProtocol } from '../V1ProofProtocol' import { V1PresentationAckMessage } from '../messages' export class V1PresentationAckHandler implements MessageHandler { - private proofService: V1ProofService + private proofProtocol: V1ProofProtocol public supportedMessages = [V1PresentationAckMessage] - public constructor(proofService: V1ProofService) { - this.proofService = proofService + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.proofService.processAck(messageContext) + await this.proofProtocol.processAck(messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts index 6918979829..e5553b1283 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts @@ -1,77 +1,69 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' import type { ProofExchangeRecord } from '../../../repository' -import type { V1ProofService } from '../V1ProofService' +import type { V1ProofProtocol } from '../V1ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' +import { DidCommMessageRepository } from '../../../../../storage' import { V1PresentationMessage, V1RequestPresentationMessage } from '../messages' export class V1PresentationHandler implements MessageHandler { - private proofService: V1ProofService - private agentConfig: AgentConfig - private proofResponseCoordinator: ProofResponseCoordinator - private didCommMessageRepository: DidCommMessageRepository + private proofProtocol: V1ProofProtocol public supportedMessages = [V1PresentationMessage] - public constructor( - proofService: V1ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - didCommMessageRepository: DidCommMessageRepository - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.didCommMessageRepository = didCommMessageRepository + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processPresentation(messageContext) + const proofRecord = await this.proofProtocol.processPresentation(messageContext) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToPresentation( - messageContext.agentContext, - proofRecord - ) + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToPresentation(messageContext.agentContext, { + presentationMessage: messageContext.message, + proofRecord, + }) if (shouldAutoRespond) { - return await this.createAck(proofRecord, messageContext) + return await this.acceptPresentation(proofRecord, messageContext) } } - private async createAck( - record: ProofExchangeRecord, + private async acceptPresentation( + proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) - - const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, { - proofRecord: record, - }) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1RequestPresentationMessage, - }) - - const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1PresentationMessage, - }) + messageContext.agentContext.config.logger.info(`Automatically sending acknowledgement with autoAccept`) if (messageContext.connection) { + const { message } = await this.proofProtocol.acceptPresentation(messageContext.agentContext, { + proofRecord, + }) + return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, connection: messageContext.connection, associatedRecord: proofRecord, }) - } else if (requestMessage?.service && presentationMessage?.service) { - const recipientService = presentationMessage?.service + } else if (messageContext.message.service) { + const { message } = await this.proofProtocol.acceptPresentation(messageContext.agentContext, { + proofRecord, + }) + + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + const requestMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V1RequestPresentationMessage, + }) + + const recipientService = messageContext.message.service const ourService = requestMessage?.service + if (!ourService) { + messageContext.agentContext.config.logger.error( + `Could not automatically create presentation ack. Missing ourService on request message` + ) + return + } + return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, serviceParams: { @@ -81,6 +73,6 @@ export class V1PresentationHandler implements MessageHandler { }) } - this.agentConfig.logger.error(`Could not automatically create presentation ack`) + messageContext.agentContext.config.logger.error(`Could not automatically create presentation ack`) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts index e3c7f97410..eee0266a68 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts @@ -1,17 +1,17 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V1ProofService } from '../V1ProofService' +import type { V1ProofProtocol } from '../V1ProofProtocol' import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' export class V1PresentationProblemReportHandler implements MessageHandler { - private proofService: V1ProofService + private proofProtocol: V1ProofProtocol public supportedMessages = [V1PresentationProblemReportMessage] - public constructor(proofService: V1ProofService) { - this.proofService = proofService + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.proofService.processProblemReport(messageContext) + await this.proofProtocol.processProblemReport(messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts index e69764d3d5..113c695c98 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts @@ -1,107 +1,44 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { IndyProofRequestFromProposalOptions } from '../../../formats/indy/IndyProofFormatsServiceOptions' -import type { ProofRequestFromProposalOptions } from '../../../models/ProofServiceOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V1ProofService } from '../V1ProofService' +import type { V1ProofProtocol } from '../V1ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' -import { AriesFrameworkError } from '../../../../../error' import { V1ProposePresentationMessage } from '../messages' export class V1ProposePresentationHandler implements MessageHandler { - private proofService: V1ProofService - private agentConfig: AgentConfig - private didCommMessageRepository: DidCommMessageRepository - private proofResponseCoordinator: ProofResponseCoordinator + private proofProtocol: V1ProofProtocol public supportedMessages = [V1ProposePresentationMessage] - public constructor( - proofService: V1ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - didCommMessageRepository: DidCommMessageRepository - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.didCommMessageRepository = didCommMessageRepository + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processProposal(messageContext) + const proofRecord = await this.proofProtocol.processProposal(messageContext) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToProposal( - messageContext.agentContext, - proofRecord - ) + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToProposal(messageContext.agentContext, { + proofRecord, + proposalMessage: messageContext.message, + }) if (shouldAutoRespond) { - return await this.createRequest(proofRecord, messageContext) + return await this.acceptProposal(proofRecord, messageContext) } } - private async createRequest( + private async acceptProposal( proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) + messageContext.agentContext.config.logger.info(`Automatically sending request with autoAccept`) if (!messageContext.connection) { - this.agentConfig.logger.error('No connection on the messageContext') - throw new AriesFrameworkError('No connection on the messageContext') - } - - const proposalMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V1ProposePresentationMessage, - }) - - if (!proposalMessage) { - this.agentConfig.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) - throw new AriesFrameworkError(`Proof record with id ${proofRecord.id} is missing required credential proposal`) + messageContext.agentContext.config.logger.error('No connection on the messageContext, aborting auto accept') + return } - const proofRequestFromProposalOptions: IndyProofRequestFromProposalOptions = { - name: 'proof-request', - version: '1.0', - nonce: await messageContext.agentContext.wallet.generateNonce(), + const { message } = await this.proofProtocol.acceptProposal(messageContext.agentContext, { proofRecord, - } - - const proofRequest: ProofRequestFromProposalOptions<[IndyProofFormat]> = - await this.proofService.createProofRequestFromProposal( - messageContext.agentContext, - proofRequestFromProposalOptions - ) - - const indyProofRequest = proofRequest.proofFormats - - if (!indyProofRequest || !indyProofRequest.indy) { - this.agentConfig.logger.error(`No Indy proof request was found`) - throw new AriesFrameworkError('No Indy proof request was found') - } - - const { message } = await this.proofService.createRequestAsResponse(messageContext.agentContext, { - proofFormats: { - indy: { - name: indyProofRequest.indy?.name, - version: indyProofRequest.indy?.version, - nonRevoked: indyProofRequest.indy?.nonRevoked, - requestedAttributes: indyProofRequest.indy?.requestedAttributes, - requestedPredicates: indyProofRequest.indy?.requestedPredicates, - ver: indyProofRequest.indy?.ver, - nonce: indyProofRequest.indy?.nonce, - }, - }, - proofRecord: proofRecord, - autoAcceptProof: proofRecord.autoAcceptProof, - willConfirm: true, }) return new OutboundMessageContext(message, { diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts index d460bbe122..340307e50a 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts @@ -1,126 +1,74 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' -import type { MediationRecipientService, RoutingService } from '../../../../routing' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { IndyProofFormat } from '../../../formats/indy/IndyProofFormat' -import type { - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../../../models/ProofServiceOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V1ProofService } from '../V1ProofService' +import type { V1ProofProtocol } from '../V1ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' -import { AriesFrameworkError } from '../../../../../error' -import { DidCommMessageRole } from '../../../../../storage' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' +import { RoutingService } from '../../../../routing' import { V1RequestPresentationMessage } from '../messages' export class V1RequestPresentationHandler implements MessageHandler { - private proofService: V1ProofService - private agentConfig: AgentConfig - private proofResponseCoordinator: ProofResponseCoordinator - private mediationRecipientService: MediationRecipientService - private didCommMessageRepository: DidCommMessageRepository - private routingService: RoutingService + private proofProtocol: V1ProofProtocol public supportedMessages = [V1RequestPresentationMessage] - public constructor( - proofService: V1ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - didCommMessageRepository: DidCommMessageRepository, - routingService: RoutingService - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.mediationRecipientService = mediationRecipientService - this.didCommMessageRepository = didCommMessageRepository - this.routingService = routingService + public constructor(proofProtocol: V1ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processRequest(messageContext) + const proofRecord = await this.proofProtocol.processRequest(messageContext) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToRequest( - messageContext.agentContext, - proofRecord - ) + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToRequest(messageContext.agentContext, { + proofRecord, + requestMessage: messageContext.message, + }) if (shouldAutoRespond) { - return await this.createPresentation(proofRecord, messageContext) + return await this.acceptRequest(proofRecord, messageContext) } } - private async createPresentation( - record: ProofExchangeRecord, + private async acceptRequest( + proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: record.id, - messageClass: V1RequestPresentationMessage, - }) - - const indyProofRequest = requestMessage.indyProofRequest - - this.agentConfig.logger.info( - `Automatically sending presentation with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) + messageContext.agentContext.config.logger.info(`Automatically sending presentation with autoAccept on`) - if (!indyProofRequest) { - this.agentConfig.logger.error('Proof request is undefined.') - throw new AriesFrameworkError('No proof request found.') - } - - const retrievedCredentials: FormatRetrievedCredentialOptions<[IndyProofFormat]> = - await this.proofService.getRequestedCredentialsForProofRequest(messageContext.agentContext, { - proofRecord: record, - config: { - filterByPresentationPreview: true, - }, + if (messageContext.connection) { + const { message } = await this.proofProtocol.acceptRequest(messageContext.agentContext, { + proofRecord, }) - if (!retrievedCredentials.proofFormats.indy) { - this.agentConfig.logger.error('No matching Indy credentials could be retrieved.') - throw new AriesFrameworkError('No matching Indy credentials could be retrieved.') - } - const options: FormatRetrievedCredentialOptions<[IndyProofFormat]> = { - proofFormats: retrievedCredentials.proofFormats, - } - const requestedCredentials: FormatRequestedCredentialReturn<[IndyProofFormat]> = - await this.proofService.autoSelectCredentialsForProofRequest(options) - - const { message, proofRecord } = await this.proofService.createPresentation(messageContext.agentContext, { - proofRecord: record, - proofFormats: { - indy: requestedCredentials.proofFormats.indy, - }, - willConfirm: true, - }) - - if (messageContext.connection) { return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, connection: messageContext.connection, associatedRecord: proofRecord, }) - } else if (requestMessage.service) { - const routing = await this.routingService.getRouting(messageContext.agentContext) - message.service = new ServiceDecorator({ + } else if (messageContext.message.service) { + const { message } = await this.proofProtocol.acceptRequest(messageContext.agentContext, { + proofRecord, + }) + + const routingService = messageContext.agentContext.dependencyManager.resolve(RoutingService) + const routing = await routingService.getRouting(messageContext.agentContext) + const ourService = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), }) - const recipientService = requestMessage.service + const recipientService = messageContext.message.service - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + // Set and save ~service decorator to record (to remember our verkey) + message.service = ourService + + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, associatedRecordId: proofRecord.id, role: DidCommMessageRole.Sender, }) + return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, serviceParams: { @@ -130,6 +78,6 @@ export class V1RequestPresentationHandler implements MessageHandler { }) } - this.agentConfig.logger.error(`Could not automatically create presentation`) + messageContext.agentContext.config.logger.error(`Could not automatically create presentation`) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/index.ts b/packages/core/src/modules/proofs/protocol/v1/index.ts index a7e92f64d6..e698bc7140 100644 --- a/packages/core/src/modules/proofs/protocol/v1/index.ts +++ b/packages/core/src/modules/proofs/protocol/v1/index.ts @@ -1,4 +1,4 @@ export * from './errors' export * from './messages' export * from './models' -export * from './V1ProofService' +export * from './V1ProofProtocol' diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts index d66360d0e5..04db86e9a5 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts @@ -1,4 +1,3 @@ -import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' import type { IndyProof } from 'indy-sdk' import { Expose, Type } from 'class-transformer' @@ -6,14 +5,11 @@ import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class import { AgentMessage } from '../../../../../agent/AgentMessage' import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { V2_INDY_PRESENTATION } from '../../../formats/ProofFormatConstants' -import { ProofFormatSpec } from '../../../models/ProofFormatSpec' export const INDY_PROOF_ATTACHMENT_ID = 'libindy-presentation-0' -export interface PresentationOptions { +export interface V1PresentationMessageOptions { id?: string comment?: string presentationAttachments: Attachment[] @@ -27,7 +23,7 @@ export interface PresentationOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#presentation */ export class V1PresentationMessage extends AgentMessage { - public constructor(options: PresentationOptions) { + public constructor(options: V1PresentationMessageOptions) { super() if (options) { @@ -61,30 +57,16 @@ export class V1PresentationMessage extends AgentMessage { @IsInstance(Attachment, { each: true }) public presentationAttachments!: Attachment[] - public getAttachmentFormats(): ProofAttachmentFormat[] { - const attachment = this.indyAttachment - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a presentation attachment`) - } - - return [ - { - format: new ProofFormatSpec({ format: V2_INDY_PRESENTATION }), - attachment: attachment, - }, - ] - } - - public get indyAttachment(): Attachment | null { - return this.presentationAttachments.find((attachment) => attachment.id === INDY_PROOF_ATTACHMENT_ID) ?? null - } - public get indyProof(): IndyProof | null { - const attachment = this.indyAttachment + const attachment = + this.presentationAttachments.find((attachment) => attachment.id === INDY_PROOF_ATTACHMENT_ID) ?? null const proofJson = attachment?.getDataAsJson() ?? null return proofJson } + + public getPresentationAttachmentById(id: string): Attachment | undefined { + return this.presentationAttachments.find((attachment) => attachment.id == id) + } } diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts index fab2d86765..bef0a44c7a 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts @@ -3,13 +3,12 @@ import { IsInstance, IsOptional, IsString, ValidateNested } from 'class-validato import { AgentMessage } from '../../../../../agent/AgentMessage' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { PresentationPreview } from '../models/V1PresentationPreview' +import { V1PresentationPreview } from '../models/V1PresentationPreview' -export interface ProposePresentationMessageOptions { +export interface V1ProposePresentationMessageOptions { id?: string comment?: string - parentThreadId?: string - presentationProposal: PresentationPreview + presentationProposal: V1PresentationPreview } /** @@ -18,17 +17,12 @@ export interface ProposePresentationMessageOptions { * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#propose-presentation */ export class V1ProposePresentationMessage extends AgentMessage { - public constructor(options: ProposePresentationMessageOptions) { + public constructor(options: V1ProposePresentationMessageOptions) { super() if (options) { this.id = options.id ?? this.generateId() this.comment = options.comment - if (options.parentThreadId) { - this.setThread({ - parentThreadId: options.parentThreadId, - }) - } this.presentationProposal = options.presentationProposal } } @@ -48,8 +42,8 @@ export class V1ProposePresentationMessage extends AgentMessage { * Represents the presentation example that prover wants to provide. */ @Expose({ name: 'presentation_proposal' }) - @Type(() => PresentationPreview) + @Type(() => V1PresentationPreview) @ValidateNested() - @IsInstance(PresentationPreview) - public presentationProposal!: PresentationPreview + @IsInstance(V1PresentationPreview) + public presentationProposal!: V1PresentationPreview } diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts index a65dae533f..5ac5fd6798 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts @@ -1,4 +1,3 @@ -import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' import type { IndyProofRequest } from 'indy-sdk' import { Expose, Type } from 'class-transformer' @@ -6,18 +5,12 @@ import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class import { AgentMessage } from '../../../../../agent/AgentMessage' import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' -import { ProofRequest } from '../../../formats/indy/models/ProofRequest' -import { ProofFormatSpec } from '../../../models/ProofFormatSpec' -export interface RequestPresentationOptions { +export interface V1RequestPresentationMessageOptions { id?: string comment?: string - parentThreadId?: string - requestPresentationAttachments: Attachment[] + requestAttachments: Attachment[] } export const INDY_PROOF_REQUEST_ATTACHMENT_ID = 'libindy-request-presentation-0' @@ -28,18 +21,13 @@ export const INDY_PROOF_REQUEST_ATTACHMENT_ID = 'libindy-request-presentation-0' * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#request-presentation */ export class V1RequestPresentationMessage extends AgentMessage { - public constructor(options: RequestPresentationOptions) { + public constructor(options: V1RequestPresentationMessageOptions) { super() if (options) { this.id = options.id ?? this.generateId() this.comment = options.comment - this.requestPresentationAttachments = options.requestPresentationAttachments - if (options.parentThreadId) { - this.setThread({ - parentThreadId: options.parentThreadId, - }) - } + this.requestAttachments = options.requestAttachments } } @@ -64,43 +52,15 @@ export class V1RequestPresentationMessage extends AgentMessage { each: true, }) @IsInstance(Attachment, { each: true }) - public requestPresentationAttachments!: Attachment[] + public requestAttachments!: Attachment[] - public get indyProofRequest(): ProofRequest | null { - // Extract proof request from attachment - const proofRequestJson = this.indyProofRequestJson - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - return proofRequest - } - - public get indyProofRequestJson(): IndyProofRequest | null { - const attachment = this.requestPresentationAttachments.find( - (attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID - ) + public get indyProofRequest(): IndyProofRequest | null { + const attachment = this.requestAttachments.find((attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID) // Extract proof request from attachment return attachment?.getDataAsJson() ?? null } - public getAttachmentFormats(): ProofAttachmentFormat[] { - const attachment = this.indyAttachment - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a request presentation attachment`) - } - - return [ - { - format: new ProofFormatSpec({ format: V2_INDY_PRESENTATION_REQUEST }), - attachment: attachment, - }, - ] - } - - public get indyAttachment(): Attachment | null { - return ( - this.requestPresentationAttachments.find((attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID) ?? - null - ) + public getRequestAttachmentById(id: string): Attachment | undefined { + return this.requestAttachments.find((attachment) => attachment.id === id) } } diff --git a/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts b/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts index 2ac71e903e..0de67b3a00 100644 --- a/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts +++ b/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts @@ -16,7 +16,7 @@ import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../../../utils/messageType' import { PredicateType } from '../../../formats/indy/models/PredicateType' -export interface PresentationPreviewAttributeOptions { +export interface V1PresentationPreviewAttributeOptions { name: string credentialDefinitionId?: string mimeType?: string @@ -24,8 +24,8 @@ export interface PresentationPreviewAttributeOptions { referent?: string } -export class PresentationPreviewAttribute { - public constructor(options: PresentationPreviewAttributeOptions) { +export class V1PresentationPreviewAttribute { + public constructor(options: V1PresentationPreviewAttributeOptions) { if (options) { this.name = options.name this.credentialDefinitionId = options.credentialDefinitionId @@ -39,7 +39,7 @@ export class PresentationPreviewAttribute { @Expose({ name: 'cred_def_id' }) @IsString() - @ValidateIf((o: PresentationPreviewAttribute) => o.referent !== undefined) + @ValidateIf((o: V1PresentationPreviewAttribute) => o.referent !== undefined) @Matches(credDefIdRegex) public credentialDefinitionId?: string @@ -61,15 +61,15 @@ export class PresentationPreviewAttribute { } } -export interface PresentationPreviewPredicateOptions { +export interface V1PresentationPreviewPredicateOptions { name: string credentialDefinitionId: string predicate: PredicateType threshold: number } -export class PresentationPreviewPredicate { - public constructor(options: PresentationPreviewPredicateOptions) { +export class V1PresentationPreviewPredicate { + public constructor(options: V1PresentationPreviewPredicateOptions) { if (options) { this.name = options.name this.credentialDefinitionId = options.credentialDefinitionId @@ -97,9 +97,9 @@ export class PresentationPreviewPredicate { } } -export interface PresentationPreviewOptions { - attributes?: PresentationPreviewAttribute[] - predicates?: PresentationPreviewPredicate[] +export interface V1PresentationPreviewOptions { + attributes?: V1PresentationPreviewAttributeOptions[] + predicates?: V1PresentationPreviewPredicateOptions[] } /** @@ -109,31 +109,31 @@ export interface PresentationPreviewOptions { * * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#presentation-preview */ -export class PresentationPreview { - public constructor(options: PresentationPreviewOptions) { +export class V1PresentationPreview { + public constructor(options: V1PresentationPreviewOptions) { if (options) { - this.attributes = options.attributes ?? [] - this.predicates = options.predicates ?? [] + this.attributes = options.attributes?.map((a) => new V1PresentationPreviewAttribute(a)) ?? [] + this.predicates = options.predicates?.map((p) => new V1PresentationPreviewPredicate(p)) ?? [] } } @Expose({ name: '@type' }) - @IsValidMessageType(PresentationPreview.type) + @IsValidMessageType(V1PresentationPreview.type) @Transform(({ value }) => replaceLegacyDidSovPrefix(value), { toClassOnly: true, }) - public readonly type = PresentationPreview.type.messageTypeUri + public readonly type = V1PresentationPreview.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/1.0/presentation-preview') - @Type(() => PresentationPreviewAttribute) + @Type(() => V1PresentationPreviewAttribute) @ValidateNested({ each: true }) - @IsInstance(PresentationPreviewAttribute, { each: true }) - public attributes!: PresentationPreviewAttribute[] + @IsInstance(V1PresentationPreviewAttribute, { each: true }) + public attributes!: V1PresentationPreviewAttribute[] - @Type(() => PresentationPreviewPredicate) + @Type(() => V1PresentationPreviewPredicate) @ValidateNested({ each: true }) - @IsInstance(PresentationPreviewPredicate, { each: true }) - public predicates!: PresentationPreviewPredicate[] + @IsInstance(V1PresentationPreviewPredicate, { each: true }) + public predicates!: V1PresentationPreviewPredicate[] public toJSON(): Record { return JsonTransformer.toJSON(this) diff --git a/packages/core/src/modules/proofs/protocol/v1/models/index.ts b/packages/core/src/modules/proofs/protocol/v1/models/index.ts index 35ec9d0545..56c1a4fde0 100644 --- a/packages/core/src/modules/proofs/protocol/v1/models/index.ts +++ b/packages/core/src/modules/proofs/protocol/v1/models/index.ts @@ -1,5 +1 @@ -export * from './PartialProof' -export * from './ProofAttribute' -export * from './ProofIdentifier' -export * from './RequestedProof' export * from './V1PresentationPreview' diff --git a/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts b/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts new file mode 100644 index 0000000000..efc6a02aad --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts @@ -0,0 +1,520 @@ +import type { AgentContext } from '../../../../agent' +import type { Attachment } from '../../../../decorators/attachment/Attachment' +import type { + ExtractProofFormats, + IndyProofFormat, + ProofFormatCredentialForRequestPayload, + ProofFormatPayload, + ProofFormatService, +} from '../../formats' +import type { ProofFormatSpec } from '../../models/ProofFormatSpec' +import type { ProofExchangeRecord } from '../../repository' + +import { AriesFrameworkError } from '../../../../error' +import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' + +import { V2PresentationMessage, V2ProposePresentationMessage, V2RequestPresentationMessage } from './messages' + +export class ProofFormatCoordinator { + /** + * Create a {@link V2ProposePresentationMessage}. + * + * @param options + * @returns The created {@link V2ProposePresentationMessage} + * + */ + public async createProposal( + agentContext: AgentContext, + { + proofFormats, + formatServices, + proofRecord, + comment, + goalCode, + }: { + formatServices: ProofFormatService[] + proofFormats: ProofFormatPayload, 'createProposal'> + proofRecord: ProofExchangeRecord + comment?: string + goalCode?: string + } + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // create message. there are two arrays in each message, one for formats the other for attachments + const formats: ProofFormatSpec[] = [] + const proposalAttachments: Attachment[] = [] + + for (const formatService of formatServices) { + const { format, attachment } = await formatService.createProposal(agentContext, { + proofFormats, + proofRecord, + }) + + proposalAttachments.push(attachment) + formats.push(format) + } + + const message = new V2ProposePresentationMessage({ + id: proofRecord.threadId, + formats, + proposalAttachments, + comment: comment, + goalCode, + }) + + message.setThread({ threadId: proofRecord.threadId, parentThreadId: proofRecord.parentThreadId }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: proofRecord.id, + }) + + return message + } + + public async processProposal( + agentContext: AgentContext, + { + proofRecord, + message, + formatServices, + }: { + proofRecord: ProofExchangeRecord + message: V2ProposePresentationMessage + formatServices: ProofFormatService[] + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + for (const formatService of formatServices) { + const attachment = this.getAttachmentForService(formatService, message.formats, message.proposalAttachments) + + await formatService.processProposal(agentContext, { + attachment, + proofRecord, + }) + } + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Receiver, + associatedRecordId: proofRecord.id, + }) + } + + public async acceptProposal( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + formatServices, + comment, + goalCode, + presentMultiple, + willConfirm, + }: { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload, 'acceptProposal'> + formatServices: ProofFormatService[] + comment?: string + goalCode?: string + presentMultiple?: boolean + willConfirm?: boolean + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // create message. there are two arrays in each message, one for formats the other for attachments + const formats: ProofFormatSpec[] = [] + const requestAttachments: Attachment[] = [] + + const proposalMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + for (const formatService of formatServices) { + const proposalAttachment = this.getAttachmentForService( + formatService, + proposalMessage.formats, + proposalMessage.proposalAttachments + ) + + const { attachment, format } = await formatService.acceptProposal(agentContext, { + proofRecord, + proofFormats, + proposalAttachment, + }) + + requestAttachments.push(attachment) + formats.push(format) + } + + const message = new V2RequestPresentationMessage({ + formats, + requestAttachments, + comment, + goalCode, + presentMultiple, + willConfirm, + }) + + message.setThread({ threadId: proofRecord.threadId, parentThreadId: proofRecord.parentThreadId }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + return message + } + + /** + * Create a {@link V2RequestPresentationMessage}. + * + * @param options + * @returns The created {@link V2RequestPresentationMessage} + * + */ + public async createRequest( + agentContext: AgentContext, + { + proofFormats, + formatServices, + proofRecord, + comment, + goalCode, + presentMultiple, + willConfirm, + }: { + formatServices: ProofFormatService[] + proofFormats: ProofFormatPayload, 'createRequest'> + proofRecord: ProofExchangeRecord + comment?: string + goalCode?: string + presentMultiple?: boolean + willConfirm?: boolean + } + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // create message. there are two arrays in each message, one for formats the other for attachments + const formats: ProofFormatSpec[] = [] + const requestAttachments: Attachment[] = [] + + for (const formatService of formatServices) { + const { format, attachment } = await formatService.createRequest(agentContext, { + proofFormats, + proofRecord, + }) + + requestAttachments.push(attachment) + formats.push(format) + } + + const message = new V2RequestPresentationMessage({ + formats, + comment, + requestAttachments, + goalCode, + presentMultiple, + willConfirm, + }) + + message.setThread({ threadId: proofRecord.threadId, parentThreadId: proofRecord.parentThreadId }) + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Sender, + associatedRecordId: proofRecord.id, + }) + + return message + } + + public async processRequest( + agentContext: AgentContext, + { + proofRecord, + message, + formatServices, + }: { + proofRecord: ProofExchangeRecord + message: V2RequestPresentationMessage + formatServices: ProofFormatService[] + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + for (const formatService of formatServices) { + const attachment = this.getAttachmentForService(formatService, message.formats, message.requestAttachments) + + await formatService.processRequest(agentContext, { + attachment, + proofRecord, + }) + } + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Receiver, + associatedRecordId: proofRecord.id, + }) + } + + public async acceptRequest( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + formatServices, + comment, + lastPresentation, + goalCode, + }: { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatPayload, 'acceptRequest'> + formatServices: ProofFormatService[] + comment?: string + lastPresentation?: boolean + goalCode?: string + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + // create message. there are two arrays in each message, one for formats the other for attachments + const formats: ProofFormatSpec[] = [] + const presentationAttachments: Attachment[] = [] + + for (const formatService of formatServices) { + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const proposalAttachment = proposalMessage + ? this.getAttachmentForService(formatService, proposalMessage.formats, proposalMessage.proposalAttachments) + : undefined + + const { attachment, format } = await formatService.acceptRequest(agentContext, { + requestAttachment, + proposalAttachment, + proofRecord, + proofFormats, + }) + + presentationAttachments.push(attachment) + formats.push(format) + } + + const message = new V2PresentationMessage({ + formats, + presentationAttachments, + comment, + lastPresentation, + goalCode, + }) + + message.setThread({ threadId: proofRecord.threadId }) + message.setPleaseAck() + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + associatedRecordId: proofRecord.id, + role: DidCommMessageRole.Sender, + }) + + return message + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + formatServices, + }: { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload< + ExtractProofFormats, + 'getCredentialsForRequest', + 'input' + > + formatServices: ProofFormatService[] + } + ): Promise, 'getCredentialsForRequest', 'output'>> { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + const credentialsForRequest: Record = {} + + for (const formatService of formatServices) { + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const proposalAttachment = proposalMessage + ? this.getAttachmentForService(formatService, proposalMessage.formats, proposalMessage.proposalAttachments) + : undefined + + const credentialsForFormat = await formatService.getCredentialsForRequest(agentContext, { + requestAttachment, + proposalAttachment, + proofRecord, + proofFormats, + }) + + credentialsForRequest[formatService.formatKey] = credentialsForFormat + } + + return credentialsForRequest + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { + proofRecord, + proofFormats, + formatServices, + }: { + proofRecord: ProofExchangeRecord + proofFormats?: ProofFormatCredentialForRequestPayload< + ExtractProofFormats, + 'selectCredentialsForRequest', + 'input' + > + formatServices: ProofFormatService[] + } + ): Promise< + ProofFormatCredentialForRequestPayload, 'selectCredentialsForRequest', 'output'> + > { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const proposalMessage = await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + const credentialsForRequest: Record = {} + + for (const formatService of formatServices) { + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const proposalAttachment = proposalMessage + ? this.getAttachmentForService(formatService, proposalMessage.formats, proposalMessage.proposalAttachments) + : undefined + + const credentialsForFormat = await formatService.selectCredentialsForRequest(agentContext, { + requestAttachment, + proposalAttachment, + proofRecord, + proofFormats, + }) + + credentialsForRequest[formatService.formatKey] = credentialsForFormat + } + + return credentialsForRequest + } + + public async processPresentation( + agentContext: AgentContext, + { + proofRecord, + message, + requestMessage, + formatServices, + }: { + proofRecord: ProofExchangeRecord + message: V2PresentationMessage + requestMessage: V2RequestPresentationMessage + formatServices: ProofFormatService[] + } + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const formatVerificationResults: boolean[] = [] + + for (const formatService of formatServices) { + const attachment = this.getAttachmentForService(formatService, message.formats, message.presentationAttachments) + const requestAttachment = this.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const isValid = await formatService.processPresentation(agentContext, { + attachment, + requestAttachment, + proofRecord, + }) + + formatVerificationResults.push(isValid) + } + + await didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { + agentMessage: message, + role: DidCommMessageRole.Receiver, + associatedRecordId: proofRecord.id, + }) + + return formatVerificationResults.every((isValid) => isValid === true) + } + + public getAttachmentForService( + credentialFormatService: ProofFormatService, + formats: ProofFormatSpec[], + attachments: Attachment[] + ) { + const attachmentId = this.getAttachmentIdForService(credentialFormatService, formats) + const attachment = attachments.find((attachment) => attachment.id === attachmentId) + + if (!attachment) { + throw new AriesFrameworkError(`Attachment with id ${attachmentId} not found in attachments.`) + } + + return attachment + } + + private getAttachmentIdForService(credentialFormatService: ProofFormatService, formats: ProofFormatSpec[]) { + const format = formats.find((format) => credentialFormatService.supportsFormat(format.format)) + + if (!format) throw new AriesFrameworkError(`No attachment found for service ${credentialFormatService.formatKey}`) + + return format.attachmentId + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts new file mode 100644 index 0000000000..7c05da4e49 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts @@ -0,0 +1,1069 @@ +import type { AgentContext } from '../../../../agent' +import type { AgentMessage } from '../../../../agent/AgentMessage' +import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' +import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' +import type { DependencyManager } from '../../../../plugins' +import type { ProblemReportMessage } from '../../../problem-reports' +import type { + ExtractProofFormats, + ProofFormat, + ProofFormatCredentialForRequestPayload, + ProofFormatPayload, +} from '../../formats' +import type { ProofFormatService } from '../../formats/ProofFormatService' +import type { ProofFormatSpec } from '../../models/ProofFormatSpec' +import type { ProofProtocol } from '../ProofProtocol' +import type { + AcceptPresentationOptions, + AcceptProofProposalOptions, + AcceptProofRequestOptions, + CreateProofProblemReportOptions, + CreateProofProposalOptions, + CreateProofRequestOptions, + ProofFormatDataMessagePayload, + GetCredentialsForRequestOptions, + GetCredentialsForRequestReturn, + GetProofFormatDataReturn, + NegotiateProofProposalOptions, + NegotiateProofRequestOptions, + ProofProtocolMsgReturnType, + SelectCredentialsForRequestOptions, + SelectCredentialsForRequestReturn, +} from '../ProofProtocolOptions' + +import { Protocol } from '../../../../agent/models' +import { AriesFrameworkError } from '../../../../error' +import { DidCommMessageRepository } from '../../../../storage' +import { uuid } from '../../../../utils/uuid' +import { AckStatus } from '../../../common' +import { ConnectionService } from '../../../connections' +import { V2ProposeCredentialMessage } from '../../../credentials' +import { ProofsModuleConfig } from '../../ProofsModuleConfig' +import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' +import { AutoAcceptProof, ProofState } from '../../models' +import { ProofExchangeRecord, ProofRepository } from '../../repository' +import { composeAutoAccept } from '../../utils/composeAutoAccept' +import { BaseProofProtocol } from '../BaseProofProtocol' + +import { ProofFormatCoordinator } from './ProofFormatCoordinator' +import { V2PresentationAckHandler } from './handlers/V2PresentationAckHandler' +import { V2PresentationHandler } from './handlers/V2PresentationHandler' +import { V2PresentationProblemReportHandler } from './handlers/V2PresentationProblemReportHandler' +import { V2ProposePresentationHandler } from './handlers/V2ProposePresentationHandler' +import { V2RequestPresentationHandler } from './handlers/V2RequestPresentationHandler' +import { V2PresentationAckMessage, V2RequestPresentationMessage } from './messages' +import { V2PresentationMessage } from './messages/V2PresentationMessage' +import { V2PresentationProblemReportMessage } from './messages/V2PresentationProblemReportMessage' +import { V2ProposePresentationMessage } from './messages/V2ProposePresentationMessage' + +export interface V2ProofProtocolConfig { + proofFormats: ProofFormatServices +} + +export class V2ProofProtocol + extends BaseProofProtocol + implements ProofProtocol +{ + private proofFormatCoordinator = new ProofFormatCoordinator() + private proofFormats: PFs + + public constructor({ proofFormats }: V2ProofProtocolConfig) { + super() + + this.proofFormats = proofFormats + } + + /** + * The version of the present proof protocol this service supports + */ + public readonly version = 'v2' as const + + public register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry) { + // Register message handlers for the Present Proof V2 Protocol + dependencyManager.registerMessageHandlers([ + new V2ProposePresentationHandler(this), + new V2RequestPresentationHandler(this), + new V2PresentationHandler(this), + new V2PresentationAckHandler(this), + new V2PresentationProblemReportHandler(this), + ]) + + // Register Present Proof V2 in feature registry, with supported roles + featureRegistry.register( + new Protocol({ + id: 'https://didcomm.org/present-proof/2.0', + roles: ['prover', 'verifier'], + }) + ) + } + + public async createProposal( + agentContext: AgentContext, + { + connectionRecord, + proofFormats, + comment, + autoAcceptProof, + goalCode, + parentThreadId, + }: CreateProofProposalOptions + ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + const formatServices = this.getFormatServices(proofFormats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to create proposal. No supported formats`) + } + + const proofRecord = new ProofExchangeRecord({ + connectionId: connectionRecord.id, + threadId: uuid(), + parentThreadId, + state: ProofState.ProposalSent, + protocolVersion: 'v2', + autoAcceptProof, + }) + + const proposalMessage = await this.proofFormatCoordinator.createProposal(agentContext, { + proofFormats, + proofRecord, + formatServices, + comment, + goalCode, + }) + + agentContext.config.logger.debug('Save record and emit state change event') + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { + proofRecord, + message: proposalMessage, + } + } + + /** + * Method called by {@link V2ProposeCredentialHandler} on reception of a propose presentation message + * We do the necessary processing here to accept the proposal and do the state change, emit event etc. + * @param messageContext the inbound propose presentation message + * @returns proof record appropriate for this incoming message (once accepted) + */ + public async processProposal( + messageContext: InboundMessageContext + ): Promise { + const { message: proposalMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing presentation proposal with id ${proposalMessage.id}`) + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + let proofRecord = await this.findByThreadAndConnectionId( + messageContext.agentContext, + proposalMessage.threadId, + connection?.id + ) + + const formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to process proposal. No supported formats`) + } + + // credential record already exists + if (proofRecord) { + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + const previousSentMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + await this.proofFormatCoordinator.processProposal(messageContext.agentContext, { + proofRecord, + formatServices, + message: proposalMessage, + }) + + await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) + + return proofRecord + } else { + // Assert + connectionService.assertConnectionOrServiceDecorator(messageContext) + + // No credential record exists with thread id + proofRecord = new ProofExchangeRecord({ + connectionId: connection?.id, + threadId: proposalMessage.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v2', + parentThreadId: proposalMessage.thread?.parentThreadId, + }) + + await this.proofFormatCoordinator.processProposal(messageContext.agentContext, { + proofRecord, + formatServices, + message: proposalMessage, + }) + + // Save record and emit event + await proofRepository.save(messageContext.agentContext, proofRecord) + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) + + return proofRecord + } + } + + public async acceptProposal( + agentContext: AgentContext, + { proofRecord, proofFormats, autoAcceptProof, comment, goalCode, willConfirm }: AcceptProofProposalOptions + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.ProposalReceived) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Use empty proofFormats if not provided to denote all formats should be accepted + let formatServices = this.getFormatServices(proofFormats ?? {}) + + // if no format services could be extracted from the proofFormats + // take all available format services from the proposal message + if (formatServices.length === 0) { + const proposalMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) + } + + // If the format services list is still empty, throw an error as we don't support any + // of the formats + if (formatServices.length === 0) { + throw new AriesFrameworkError( + `Unable to accept proposal. No supported formats provided as input or in proposal message` + ) + } + + const requestMessage = await this.proofFormatCoordinator.acceptProposal(agentContext, { + proofRecord, + formatServices, + comment, + proofFormats, + goalCode, + willConfirm, + // Not supported at the moment + presentMultiple: false, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { proofRecord, message: requestMessage } + } + + /** + * Negotiate a proof proposal as verifier (by sending a proof request message) to the connection + * associated with the proof record. + * + * @param options configuration for the request see {@link NegotiateProofProposalOptions} + * @returns Proof exchange record associated with the proof request + * + */ + public async negotiateProposal( + agentContext: AgentContext, + { proofRecord, proofFormats, autoAcceptProof, comment, goalCode, willConfirm }: NegotiateProofProposalOptions + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.ProposalReceived) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support negotiation.` + ) + } + + const formatServices = this.getFormatServices(proofFormats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to create request. No supported formats`) + } + + const requestMessage = await this.proofFormatCoordinator.createRequest(agentContext, { + formatServices, + proofFormats, + proofRecord, + comment, + goalCode, + willConfirm, + // Not supported at the moment + presentMultiple: false, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.RequestSent) + + return { proofRecord, message: requestMessage } + } + + /** + * Create a {@link V2RequestPresentationMessage} as beginning of protocol process. + * @returns Object containing request message and associated credential record + * + */ + public async createRequest( + agentContext: AgentContext, + { + proofFormats, + autoAcceptProof, + comment, + connectionRecord, + parentThreadId, + goalCode, + willConfirm, + }: CreateProofRequestOptions + ): Promise> { + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + + const formatServices = this.getFormatServices(proofFormats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to create request. No supported formats`) + } + + const proofRecord = new ProofExchangeRecord({ + connectionId: connectionRecord?.id, + threadId: uuid(), + state: ProofState.RequestSent, + autoAcceptProof, + protocolVersion: 'v2', + parentThreadId, + }) + + const requestMessage = await this.proofFormatCoordinator.createRequest(agentContext, { + formatServices, + proofFormats, + proofRecord, + comment, + goalCode, + willConfirm, + }) + + agentContext.config.logger.debug( + `Saving record and emitting state changed for proof exchange record ${proofRecord.id}` + ) + await proofRepository.save(agentContext, proofRecord) + this.emitStateChangedEvent(agentContext, proofRecord, null) + + return { proofRecord, message: requestMessage } + } + + /** + * Process a received {@link V2RequestPresentationMessage}. This will not accept the proof request + * or send a proof. It will only update the existing proof record with + * the information from the proof request message. Use {@link createCredential} + * after calling this method to create a proof. + *z + * @param messageContext The message context containing a v2 proof request message + * @returns proof record associated with the proof request message + * + */ + public async processRequest( + messageContext: InboundMessageContext + ): Promise { + const { message: requestMessage, connection, agentContext } = messageContext + + const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing proof request with id ${requestMessage.id}`) + + let proofRecord = await this.findByThreadAndConnectionId( + messageContext.agentContext, + requestMessage.threadId, + connection?.id + ) + + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to process request. No supported formats`) + } + + // proof record already exists + if (proofRecord) { + const previousSentMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposeCredentialMessage, + }) + + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.ProposalSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + await this.proofFormatCoordinator.processRequest(messageContext.agentContext, { + proofRecord, + formatServices, + message: requestMessage, + }) + + await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) + return proofRecord + } else { + // Assert + connectionService.assertConnectionOrServiceDecorator(messageContext) + + // No proof record exists with thread id + agentContext.config.logger.debug('No proof record found for request, creating a new one') + proofRecord = new ProofExchangeRecord({ + connectionId: connection?.id, + threadId: requestMessage.threadId, + state: ProofState.RequestReceived, + protocolVersion: 'v2', + parentThreadId: requestMessage.thread?.parentThreadId, + }) + + await this.proofFormatCoordinator.processRequest(messageContext.agentContext, { + proofRecord, + formatServices, + message: requestMessage, + }) + + // Save in repository + agentContext.config.logger.debug('Saving proof record and emit request-received event') + await proofRepository.save(messageContext.agentContext, proofRecord) + + this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) + return proofRecord + } + } + + public async acceptRequest( + agentContext: AgentContext, + { proofRecord, autoAcceptProof, comment, proofFormats, goalCode }: AcceptProofRequestOptions + ) { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestReceived) + + // Use empty proofFormats if not provided to denote all formats should be accepted + let formatServices = this.getFormatServices(proofFormats ?? {}) + + // if no format services could be extracted from the proofFormats + // take all available format services from the request message + if (formatServices.length === 0) { + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + } + + // If the format services list is still empty, throw an error as we don't support any + // of the formats + if (formatServices.length === 0) { + throw new AriesFrameworkError( + `Unable to accept request. No supported formats provided as input or in request message` + ) + } + const message = await this.proofFormatCoordinator.acceptRequest(agentContext, { + proofRecord, + formatServices, + comment, + proofFormats, + goalCode, + // Sending multiple presentation messages not supported at the moment + lastPresentation: true, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.PresentationSent) + + return { proofRecord, message } + } + + /** + * Create a {@link V2ProposePresentationMessage} as response to a received credential request. + * To create a proposal not bound to an existing proof exchange, use {@link createProposal}. + * + * @param options configuration to use for the proposal + * @returns Object containing proposal message and associated proof record + * + */ + public async negotiateRequest( + agentContext: AgentContext, + { proofRecord, proofFormats, autoAcceptProof, comment, goalCode }: NegotiateProofRequestOptions + ): Promise> { + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestReceived) + + if (!proofRecord.connectionId) { + throw new AriesFrameworkError( + `No connectionId found for proof record '${proofRecord.id}'. Connection-less verification does not support negotiation.` + ) + } + + const formatServices = this.getFormatServices(proofFormats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to create proposal. No supported formats`) + } + + const proposalMessage = await this.proofFormatCoordinator.createProposal(agentContext, { + formatServices, + proofFormats, + proofRecord, + comment, + goalCode, + }) + + proofRecord.autoAcceptProof = autoAcceptProof ?? proofRecord.autoAcceptProof + await this.updateState(agentContext, proofRecord, ProofState.ProposalSent) + + return { proofRecord, message: proposalMessage } + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { proofRecord, proofFormats }: GetCredentialsForRequestOptions + ): Promise> { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestReceived) + + // Use empty proofFormats if not provided to denote all formats should be accepted + let formatServices = this.getFormatServices(proofFormats ?? {}) + + // if no format services could be extracted from the proofFormats + // take all available format services from the request message + if (formatServices.length === 0) { + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + } + + // If the format services list is still empty, throw an error as we don't support any + // of the formats + if (formatServices.length === 0) { + throw new AriesFrameworkError( + `Unable to get credentials for request. No supported formats provided as input or in request message` + ) + } + + const result = await this.proofFormatCoordinator.getCredentialsForRequest(agentContext, { + formatServices, + proofFormats, + proofRecord, + }) + + return { + proofFormats: result, + } + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { proofRecord, proofFormats }: SelectCredentialsForRequestOptions + ): Promise> { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestReceived) + + // Use empty proofFormats if not provided to denote all formats should be accepted + let formatServices = this.getFormatServices(proofFormats ?? {}) + + // if no format services could be extracted from the proofFormats + // take all available format services from the request message + if (formatServices.length === 0) { + const requestMessage = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + } + + // If the format services list is still empty, throw an error as we don't support any + // of the formats + if (formatServices.length === 0) { + throw new AriesFrameworkError( + `Unable to get credentials for request. No supported formats provided as input or in request message` + ) + } + + const result = await this.proofFormatCoordinator.selectCredentialsForRequest(agentContext, { + formatServices, + proofFormats, + proofRecord, + }) + + return { + proofFormats: result, + } + } + + public async processPresentation( + messageContext: InboundMessageContext + ): Promise { + const { message: presentationMessage, connection, agentContext } = messageContext + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + agentContext.config.logger.debug(`Processing presentation with id ${presentationMessage.id}`) + + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + presentationMessage.threadId, + connection?.id + ) + + const previousSentMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const previousReceivedMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2ProposePresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.RequestSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + const formatServices = this.getFormatServicesFromMessage(presentationMessage.formats) + if (formatServices.length === 0) { + throw new AriesFrameworkError(`Unable to process presentation. No supported formats`) + } + + const isValid = await this.proofFormatCoordinator.processPresentation(messageContext.agentContext, { + proofRecord, + formatServices, + requestMessage: previousSentMessage, + message: presentationMessage, + }) + + proofRecord.isVerified = isValid + await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) + + return proofRecord + } + + public async acceptPresentation( + agentContext: AgentContext, + { proofRecord }: AcceptPresentationOptions + ): Promise> { + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.PresentationReceived) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + // assert we've received the final presentation + const presentation = await didCommMessageRepository.getAgentMessage(agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2PresentationMessage, + }) + + if (!presentation.lastPresentation) { + throw new AriesFrameworkError( + `Trying to send an ack message while presentation with id ${presentation.id} indicates this is not the last presentation (presentation.last_presentation is set to false)` + ) + } + + const message = new V2PresentationAckMessage({ + threadId: proofRecord.threadId, + status: AckStatus.OK, + }) + + await this.updateState(agentContext, proofRecord, ProofState.Done) + + return { + message, + proofRecord, + } + } + + public async processAck( + messageContext: InboundMessageContext + ): Promise { + const { message: ackMessage, connection, agentContext } = messageContext + + agentContext.config.logger.debug(`Processing proof ack with id ${ackMessage.id}`) + + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + const connectionService = agentContext.dependencyManager.resolve(ConnectionService) + + const proofRecord = await this.getByThreadAndConnectionId( + messageContext.agentContext, + ackMessage.threadId, + connection?.id + ) + proofRecord.connectionId = connection?.id + + const previousReceivedMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2RequestPresentationMessage, + }) + + const previousSentMessage = await didCommMessageRepository.getAgentMessage(messageContext.agentContext, { + associatedRecordId: proofRecord.id, + messageClass: V2PresentationMessage, + }) + + // Assert + proofRecord.assertProtocolVersion('v2') + proofRecord.assertState(ProofState.PresentationSent) + connectionService.assertConnectionOrServiceDecorator(messageContext, { + previousReceivedMessage, + previousSentMessage, + }) + + // Update record + await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) + + return proofRecord + } + + public async createProblemReport( + agentContext: AgentContext, + { description, proofRecord }: CreateProofProblemReportOptions + ): Promise> { + const message = new V2PresentationProblemReportMessage({ + description: { + en: description, + code: PresentationProblemReportReason.Abandoned, + }, + }) + + message.setThread({ + threadId: proofRecord.threadId, + parentThreadId: proofRecord.parentThreadId, + }) + + return { + proofRecord, + message, + } + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + proposalMessage: V2ProposePresentationMessage + } + ): Promise { + const { proofRecord, proposalMessage } = options + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const requestMessage = await this.findRequestMessage(agentContext, proofRecord.id) + if (!requestMessage) return false + + // NOTE: we take the formats from the requestMessage so we always check all services that we last sent + // Otherwise we'll only check the formats from the proposal, which could be different from the formats + // we use. + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + + for (const formatService of formatServices) { + const requestAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const proposalAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + proposalMessage.formats, + proposalMessage.proposalAttachments + ) + + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToProposal(agentContext, { + proofRecord, + requestAttachment, + proposalAttachment, + }) + // If any of the formats return false, we should not auto accept + if (!shouldAutoRespondToFormat) return false + } + + return true + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + options: { + proofRecord: ProofExchangeRecord + requestMessage: V2RequestPresentationMessage + } + ): Promise { + const { proofRecord, requestMessage } = options + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const proposalMessage = await this.findProposalMessage(agentContext, proofRecord.id) + if (!proposalMessage) return false + + // NOTE: we take the formats from the proposalMessage so we always check all services that we last sent + // Otherwise we'll only check the formats from the request, which could be different from the formats + // we use. + const formatServices = this.getFormatServicesFromMessage(proposalMessage.formats) + + for (const formatService of formatServices) { + const proposalAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + proposalMessage.formats, + proposalMessage.proposalAttachments + ) + + const requestAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToRequest(agentContext, { + proofRecord, + requestAttachment, + proposalAttachment, + }) + + // If any of the formats return false, we should not auto accept + if (!shouldAutoRespondToFormat) return false + } + + return true + } + + public async shouldAutoRespondToPresentation( + agentContext: AgentContext, + options: { proofRecord: ProofExchangeRecord; presentationMessage: V2PresentationMessage } + ): Promise { + const { proofRecord, presentationMessage } = options + const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) + + // If this isn't the last presentation yet, we should not auto accept + if (!presentationMessage.lastPresentation) return false + + const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + + // Handle always / never cases + if (autoAccept === AutoAcceptProof.Always) return true + if (autoAccept === AutoAcceptProof.Never) return false + + const proposalMessage = await this.findProposalMessage(agentContext, proofRecord.id) + + const requestMessage = await this.findRequestMessage(agentContext, proofRecord.id) + if (!requestMessage) return false + if (!requestMessage.willConfirm) return false + + // NOTE: we take the formats from the requestMessage so we always check all services that we last sent + // Otherwise we'll only check the formats from the credential, which could be different from the formats + // we use. + const formatServices = this.getFormatServicesFromMessage(requestMessage.formats) + + for (const formatService of formatServices) { + const proposalAttachment = proposalMessage + ? this.proofFormatCoordinator.getAttachmentForService( + formatService, + proposalMessage.formats, + proposalMessage.proposalAttachments + ) + : undefined + + const requestAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + requestMessage.formats, + requestMessage.requestAttachments + ) + + const presentationAttachment = this.proofFormatCoordinator.getAttachmentForService( + formatService, + presentationMessage.formats, + presentationMessage.presentationAttachments + ) + + const shouldAutoRespondToFormat = await formatService.shouldAutoRespondToPresentation(agentContext, { + proofRecord, + presentationAttachment, + requestAttachment, + proposalAttachment, + }) + + // If any of the formats return false, we should not auto accept + if (!shouldAutoRespondToFormat) return false + } + return true + } + + public async findRequestMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2RequestPresentationMessage, + }) + } + + public async findPresentationMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2PresentationMessage, + }) + } + + public async findProposalMessage( + agentContext: AgentContext, + proofRecordId: string + ): Promise { + const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) + + return await didCommMessageRepository.findAgentMessage(agentContext, { + associatedRecordId: proofRecordId, + messageClass: V2ProposePresentationMessage, + }) + } + + public async getFormatData(agentContext: AgentContext, proofRecordId: string): Promise { + // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. + const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ + this.findProposalMessage(agentContext, proofRecordId), + this.findRequestMessage(agentContext, proofRecordId), + this.findPresentationMessage(agentContext, proofRecordId), + ]) + + // Create object with the keys and the message formats/attachments. We can then loop over this in a generic + // way so we don't have to add the same operation code four times + const messages = { + proposal: [proposalMessage?.formats, proposalMessage?.proposalAttachments], + request: [requestMessage?.formats, requestMessage?.requestAttachments], + presentation: [presentationMessage?.formats, presentationMessage?.presentationAttachments], + } as const + + const formatData: GetProofFormatDataReturn = {} + + // We loop through all of the message keys as defined above + for (const [messageKey, [formats, attachments]] of Object.entries(messages)) { + // Message can be undefined, so we continue if it is not defined + if (!formats || !attachments) continue + + // Find all format services associated with the message + const formatServices = this.getFormatServicesFromMessage(formats) + + const messageFormatData: ProofFormatDataMessagePayload = {} + + // Loop through all of the format services, for each we will extract the attachment data and assign this to the object + // using the unique format key (e.g. indy) + for (const formatService of formatServices) { + const attachment = this.proofFormatCoordinator.getAttachmentForService(formatService, formats, attachments) + messageFormatData[formatService.formatKey] = attachment.getDataAsJson() + } + + formatData[messageKey as keyof GetProofFormatDataReturn] = messageFormatData + } + + return formatData + } + + /** + * Get all the format service objects for a given proof format from an incoming message + * @param messageFormats the format objects containing the format name (eg indy) + * @return the proof format service objects in an array - derived from format object keys + */ + private getFormatServicesFromMessage(messageFormats: ProofFormatSpec[]): ProofFormatService[] { + const formatServices = new Set() + + for (const msg of messageFormats) { + const service = this.getFormatServiceForFormat(msg.format) + if (service) formatServices.add(service) + } + + return Array.from(formatServices) + } + + /** + * Get all the format service objects for a given proof format + * @param proofFormats the format object containing various optional parameters + * @return the proof format service objects in an array - derived from format object keys + */ + private getFormatServices( + proofFormats: M extends 'selectCredentialsForRequest' | 'getCredentialsForRequest' + ? ProofFormatCredentialForRequestPayload, M, 'input'> + : ProofFormatPayload, M> + ): ProofFormatService[] { + const formats = new Set() + + for (const formatKey of Object.keys(proofFormats)) { + const formatService = this.getFormatServiceForFormatKey(formatKey) + + if (formatService) formats.add(formatService) + } + + return Array.from(formats) + } + + private getFormatServiceForFormatKey(formatKey: string): ProofFormatService | null { + const formatService = this.proofFormats.find((proofFormats) => proofFormats.formatKey === formatKey) + + return formatService ?? null + } + + private getFormatServiceForFormat(format: string): ProofFormatService | null { + const formatService = this.proofFormats.find((proofFormats) => proofFormats.supportsFormat(format)) + + return formatService ?? null + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts deleted file mode 100644 index cfe6ae8e43..0000000000 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofService.ts +++ /dev/null @@ -1,953 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { AgentMessage } from '../../../../agent/AgentMessage' -import type { Dispatcher } from '../../../../agent/Dispatcher' -import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import type { Attachment } from '../../../../decorators/attachment/Attachment' -import type { MediationRecipientService } from '../../../routing/services/MediationRecipientService' -import type { RoutingService } from '../../../routing/services/RoutingService' -import type { ProofResponseCoordinator } from '../../ProofResponseCoordinator' -import type { ProofFormatServiceMap } from '../../formats' -import type { ProofFormat } from '../../formats/ProofFormat' -import type { ProofFormatService } from '../../formats/ProofFormatService' -import type { CreateProblemReportOptions } from '../../formats/models/ProofFormatServiceOptions' -import type { ProofFormatSpec } from '../../models/ProofFormatSpec' -import type { - CreateAckOptions, - CreatePresentationOptions, - CreateProofRequestFromProposalOptions, - CreateProposalAsResponseOptions, - CreateProposalOptions, - CreateRequestAsResponseOptions, - CreateRequestOptions, - FormatDataMessagePayload, - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, - GetFormatDataReturn, - GetRequestedCredentialsForProofRequestOptions, - ProofRequestFromProposalOptions, -} from '../../models/ProofServiceOptions' - -import { inject, Lifecycle, scoped } from 'tsyringe' - -import { AgentConfig } from '../../../../agent/AgentConfig' -import { EventEmitter } from '../../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../../constants' -import { AriesFrameworkError } from '../../../../error' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { Wallet } from '../../../../wallet/Wallet' -import { AckStatus } from '../../../common' -import { ConnectionService } from '../../../connections' -import { ProofService } from '../../ProofService' -import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' -import { V2_INDY_PRESENTATION_REQUEST } from '../../formats/ProofFormatConstants' -import { IndyProofFormatService } from '../../formats/indy/IndyProofFormatService' -import { ProofState } from '../../models/ProofState' -import { ProofExchangeRecord, ProofRepository } from '../../repository' - -import { V2PresentationProblemReportError } from './errors' -import { V2PresentationAckHandler } from './handlers/V2PresentationAckHandler' -import { V2PresentationHandler } from './handlers/V2PresentationHandler' -import { V2PresentationProblemReportHandler } from './handlers/V2PresentationProblemReportHandler' -import { V2ProposePresentationHandler } from './handlers/V2ProposePresentationHandler' -import { V2RequestPresentationHandler } from './handlers/V2RequestPresentationHandler' -import { V2PresentationAckMessage } from './messages' -import { V2PresentationMessage } from './messages/V2PresentationMessage' -import { V2PresentationProblemReportMessage } from './messages/V2PresentationProblemReportMessage' -import { V2ProposalPresentationMessage } from './messages/V2ProposalPresentationMessage' -import { V2RequestPresentationMessage } from './messages/V2RequestPresentationMessage' - -@scoped(Lifecycle.ContainerScoped) -export class V2ProofService extends ProofService { - private formatServiceMap: { [key: string]: ProofFormatService } - - public constructor( - agentConfig: AgentConfig, - connectionService: ConnectionService, - proofRepository: ProofRepository, - didCommMessageRepository: DidCommMessageRepository, - eventEmitter: EventEmitter, - indyProofFormatService: IndyProofFormatService, - @inject(InjectionSymbols.Wallet) wallet: Wallet - ) { - super(agentConfig, proofRepository, connectionService, didCommMessageRepository, wallet, eventEmitter) - this.wallet = wallet - // Dynamically build format service map. This will be extracted once services are registered dynamically - this.formatServiceMap = [indyProofFormatService].reduce( - (formatServiceMap, formatService) => ({ - ...formatServiceMap, - [formatService.formatKey]: formatService, - }), - {} - ) as ProofFormatServiceMap - } - - /** - * The version of the present proof protocol this service supports - */ - public readonly version = 'v2' as const - - public async createProposal( - agentContext: AgentContext, - options: CreateProposalOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - const formats = [] - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - formats.push(await service.createProposal({ formats: options.proofFormats })) - } - - const proposalMessage = new V2ProposalPresentationMessage({ - attachmentInfo: formats, - comment: options.comment, - willConfirm: options.willConfirm, - goalCode: options.goalCode, - parentThreadId: options.parentThreadId, - }) - - const proofRecord = new ProofExchangeRecord({ - connectionId: options.connectionRecord.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalSent, - protocolVersion: 'v2', - }) - - await this.proofRepository.save(agentContext, proofRecord) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: proposalMessage, - role: DidCommMessageRole.Sender, - associatedRecordId: proofRecord.id, - }) - - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { - proofRecord: proofRecord, - message: proposalMessage, - } - } - - public async createProposalAsResponse( - agentContext: AgentContext, - options: CreateProposalAsResponseOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - options.proofRecord.assertState(ProofState.RequestReceived) - - const formats = [] - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - formats.push( - await service.createProposal({ - formats: options.proofFormats, - }) - ) - } - - const proposalMessage = new V2ProposalPresentationMessage({ - attachmentInfo: formats, - comment: options.comment, - goalCode: options.goalCode, - willConfirm: options.willConfirm, - }) - - proposalMessage.setThread({ threadId: options.proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: proposalMessage, - role: DidCommMessageRole.Sender, - associatedRecordId: options.proofRecord.id, - }) - - await this.updateState(agentContext, options.proofRecord, ProofState.ProposalSent) - - return { message: proposalMessage, proofRecord: options.proofRecord } - } - - public async processProposal( - messageContext: InboundMessageContext - ): Promise { - const { message: proposalMessage, connection: connectionRecord } = messageContext - let proofRecord: ProofExchangeRecord - - const proposalAttachments = proposalMessage.getAttachmentFormats() - - for (const attachmentFormat of proposalAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - await service?.processProposal({ - proposal: attachmentFormat, - }) - } - - try { - proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: proposalMessage.threadId, - connectionId: connectionRecord?.id, - }) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalMessage, - previousSentMessage: requestMessage ?? undefined, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - await this.updateState(messageContext.agentContext, proofRecord, ProofState.ProposalReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofExchangeRecord({ - connectionId: connectionRecord?.id, - threadId: proposalMessage.threadId, - parentThreadId: proposalMessage.thread?.parentThreadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v2', - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save record - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proposalMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - await this.proofRepository.save(messageContext.agentContext, proofRecord) - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - public async createRequest( - agentContext: AgentContext, - options: CreateRequestOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - // create attachment formats - const formats = [] - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - formats.push( - await service.createRequest({ - formats: options.proofFormats, - }) - ) - } - - // create request message - const requestMessage = new V2RequestPresentationMessage({ - attachmentInfo: formats, - comment: options.comment, - willConfirm: options.willConfirm, - goalCode: options.goalCode, - parentThreadId: options.parentThreadId, - }) - - // create & store proof record - const proofRecord = new ProofExchangeRecord({ - connectionId: options.connectionRecord?.id, - threadId: requestMessage.threadId, - parentThreadId: requestMessage.thread?.parentThreadId, - state: ProofState.RequestSent, - protocolVersion: 'v2', - }) - - await this.proofRepository.save(agentContext, proofRecord) - - // create DIDComm message - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: requestMessage, - role: DidCommMessageRole.Sender, - associatedRecordId: proofRecord.id, - }) - - this.emitStateChangedEvent(agentContext, proofRecord, null) - - return { - proofRecord: proofRecord, - message: requestMessage, - } - } - - public async createRequestAsResponse( - agentContext: AgentContext, - options: CreateRequestAsResponseOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - options.proofRecord.assertState(ProofState.ProposalReceived) - - const proposal = await this.didCommMessageRepository.getAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposal) { - throw new AriesFrameworkError( - `Proof record with id ${options.proofRecord.id} is missing required presentation proposal` - ) - } - - // create attachment formats - const formats = [] - - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - const requestOptions: CreateRequestAsResponseOptions = { - proofFormats: options.proofFormats, - proofRecord: options.proofRecord, - } - formats.push(await service.createRequestAsResponse(requestOptions)) - } - - // create request message - const requestMessage = new V2RequestPresentationMessage({ - attachmentInfo: formats, - comment: options.comment, - willConfirm: options.willConfirm, - goalCode: options.goalCode, - }) - requestMessage.setThread({ threadId: options.proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: requestMessage, - role: DidCommMessageRole.Sender, - associatedRecordId: options.proofRecord.id, - }) - - await this.updateState(agentContext, options.proofRecord, ProofState.RequestSent) - - return { message: requestMessage, proofRecord: options.proofRecord } - } - - public async processRequest( - messageContext: InboundMessageContext - ): Promise { - const { message: proofRequestMessage, connection: connectionRecord } = messageContext - - const requestAttachments = proofRequestMessage.getAttachmentFormats() - - for (const attachmentFormat of requestAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - await service?.processRequest({ - requestAttachment: attachmentFormat, - }) - } - - // assert - if (proofRequestMessage.requestPresentationsAttach.length === 0) { - throw new V2PresentationProblemReportError( - `Missing required base64 or json encoded attachment data for presentation request with thread id ${proofRequestMessage.threadId}`, - { problemCode: PresentationProblemReportReason.Abandoned } - ) - } - - this.logger.debug(`Received proof request`, proofRequestMessage) - - let proofRecord: ProofExchangeRecord - - try { - proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: proofRequestMessage.threadId, - connectionId: connectionRecord?.id, - }) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.ProposalSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: requestMessage ?? undefined, - previousSentMessage: proposalMessage ?? undefined, - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proofRequestMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.RequestReceived) - } catch { - // No proof record exists with thread id - proofRecord = new ProofExchangeRecord({ - connectionId: connectionRecord?.id, - threadId: proofRequestMessage.threadId, - parentThreadId: proofRequestMessage.thread?.parentThreadId, - state: ProofState.RequestReceived, - protocolVersion: 'v2', - }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: proofRequestMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Assert - this.connectionService.assertConnectionOrServiceDecorator(messageContext) - - // Save in repository - await this.proofRepository.save(messageContext.agentContext, proofRecord) - this.emitStateChangedEvent(messageContext.agentContext, proofRecord, null) - } - - return proofRecord - } - - public async createPresentation( - agentContext: AgentContext, - options: CreatePresentationOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - // assert state - options.proofRecord.assertState(ProofState.RequestReceived) - - const proofRequest = await this.didCommMessageRepository.getAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - const formats = [] - for (const key of Object.keys(options.proofFormats)) { - const service = this.formatServiceMap[key] - formats.push( - await service.createPresentation(agentContext, { - attachment: proofRequest.getAttachmentByFormatIdentifier(V2_INDY_PRESENTATION_REQUEST), - proofFormats: options.proofFormats, - }) - ) - } - - const presentationMessage = new V2PresentationMessage({ - comment: options.comment, - attachmentInfo: formats, - goalCode: options.goalCode, - lastPresentation: options.lastPresentation, - }) - presentationMessage.setThread({ threadId: options.proofRecord.threadId }) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(agentContext, { - agentMessage: presentationMessage, - associatedRecordId: options.proofRecord.id, - role: DidCommMessageRole.Sender, - }) - - await this.updateState(agentContext, options.proofRecord, ProofState.PresentationSent) - - return { message: presentationMessage, proofRecord: options.proofRecord } - } - - public async processPresentation( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationMessage, connection: connectionRecord } = messageContext - - this.logger.debug(`Processing presentation with id ${presentationMessage.id}`) - - const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: presentationMessage.threadId, - connectionId: connectionRecord?.id, - }) - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.RequestSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: proposalMessage ?? undefined, - previousSentMessage: requestMessage ?? undefined, - }) - - const formatVerificationResults = [] - for (const attachmentFormat of presentationMessage.getAttachmentFormats()) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - if (service) { - try { - formatVerificationResults.push( - await service.processPresentation(messageContext.agentContext, { - record: proofRecord, - formatAttachments: { - request: requestMessage?.getAttachmentFormats(), - presentation: presentationMessage.getAttachmentFormats(), - }, - }) - ) - } catch (e) { - if (e instanceof AriesFrameworkError) { - throw new V2PresentationProblemReportError(e.message, { - problemCode: PresentationProblemReportReason.Abandoned, - }) - } - throw e - } - } - } - if (formatVerificationResults.length === 0) { - throw new V2PresentationProblemReportError('None of the received formats are supported.', { - problemCode: PresentationProblemReportReason.Abandoned, - }) - } - - const isValid = formatVerificationResults.every((x) => x === true) - - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { - agentMessage: presentationMessage, - associatedRecordId: proofRecord.id, - role: DidCommMessageRole.Receiver, - }) - - // Update record - proofRecord.isVerified = isValid - await this.updateState(messageContext.agentContext, proofRecord, ProofState.PresentationReceived) - - return proofRecord - } - - public async createAck( - agentContext: AgentContext, - options: CreateAckOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - // assert we've received the final presentation - const presentation = await this.didCommMessageRepository.getAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V2PresentationMessage, - }) - - if (!presentation.lastPresentation) { - throw new AriesFrameworkError( - `Trying to send an ack message while presentation with id ${presentation.id} indicates this is not the last presentation (presentation.lastPresentation is set to false)` - ) - } - - const message = new V2PresentationAckMessage({ - threadId: options.proofRecord.threadId, - status: AckStatus.OK, - }) - - await this.updateState(agentContext, options.proofRecord, ProofState.Done) - - return { - message, - proofRecord: options.proofRecord, - } - } - - public async processAck( - messageContext: InboundMessageContext - ): Promise { - const { message: ackMessage, connection: connectionRecord } = messageContext - - const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: ackMessage.threadId, - connectionId: connectionRecord?.id, - }) - - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2PresentationMessage, - }) - - // Assert - proofRecord.assertState(ProofState.PresentationSent) - this.connectionService.assertConnectionOrServiceDecorator(messageContext, { - previousReceivedMessage: requestMessage ?? undefined, - previousSentMessage: presentationMessage ?? undefined, - }) - - // Update record - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Done) - - return proofRecord - } - - public async createProblemReport( - agentContext: AgentContext, - options: CreateProblemReportOptions - ): Promise<{ proofRecord: ProofExchangeRecord; message: AgentMessage }> { - const msg = new V2PresentationProblemReportMessage({ - description: { - code: PresentationProblemReportReason.Abandoned, - en: options.description, - }, - }) - - msg.setThread({ - threadId: options.proofRecord.threadId, - parentThreadId: options.proofRecord.threadId, - }) - - return { - proofRecord: options.proofRecord, - message: msg, - } - } - - public async processProblemReport( - messageContext: InboundMessageContext - ): Promise { - const { message: presentationProblemReportMessage } = messageContext - - const connectionRecord = messageContext.assertReadyConnection() - - this.logger.debug(`Processing problem report with id ${presentationProblemReportMessage.id}`) - - const proofRecord = await this.proofRepository.getSingleByQuery(messageContext.agentContext, { - threadId: presentationProblemReportMessage.threadId, - connectionId: connectionRecord?.id, - }) - - proofRecord.errorMessage = `${presentationProblemReportMessage.description.code}: ${presentationProblemReportMessage.description.en}` - await this.updateState(messageContext.agentContext, proofRecord, ProofState.Abandoned) - return proofRecord - } - - public async createProofRequestFromProposal( - agentContext: AgentContext, - options: CreateProofRequestFromProposalOptions - ): Promise> { - const proofRecordId = options.proofRecord.id - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposalMessage) { - throw new AriesFrameworkError(`Proof record with id ${proofRecordId} is missing required presentation proposal`) - } - - const proposalAttachments = proposalMessage.getAttachmentFormats() - - let result = {} - - for (const attachmentFormat of proposalAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - - if (!service) { - throw new AriesFrameworkError('No format service found for getting requested.') - } - - result = { - ...result, - ...(await service.createProofRequestFromProposal({ - presentationAttachment: attachmentFormat.attachment, - })), - } - } - - const retVal: ProofRequestFromProposalOptions = { - proofRecord: options.proofRecord, - proofFormats: result, - } - return retVal - } - - public async shouldAutoRespondToProposal( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposal) return false - - const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - if (!request) return false - - MessageValidator.validateSync(proposal) - - const proposalAttachments = proposal.getAttachmentFormats() - const requestAttachments = request.getAttachmentFormats() - - const equalityResults = [] - for (const attachmentFormat of proposalAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - equalityResults.push(service?.proposalAndRequestAreEqual(proposalAttachments, requestAttachments)) - } - return true - } - - public async shouldAutoRespondToRequest( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const proposal = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposal) { - return false - } - - const request = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - if (!request) { - throw new AriesFrameworkError( - `Expected to find a request message for ProofExchangeRecord with id ${proofRecord.id}` - ) - } - - const proposalAttachments = proposal.getAttachmentFormats() - const requestAttachments = request.getAttachmentFormats() - - const equalityResults = [] - for (const attachmentFormat of proposalAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - equalityResults.push(service?.proposalAndRequestAreEqual(proposalAttachments, requestAttachments)) - } - - return equalityResults.every((x) => x === true) - } - - public async shouldAutoRespondToPresentation( - agentContext: AgentContext, - proofRecord: ProofExchangeRecord - ): Promise { - const request = await this.didCommMessageRepository.getAgentMessage(agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - return request.willConfirm - } - - public async findRequestMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V2RequestPresentationMessage, - }) - } - - public async findPresentationMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V2PresentationMessage, - }) - } - - public async findProposalMessage( - agentContext: AgentContext, - proofRecordId: string - ): Promise { - return await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: proofRecordId, - messageClass: V2ProposalPresentationMessage, - }) - } - - public async getFormatData(agentContext: AgentContext, proofRecordId: string): Promise { - // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. - const [proposalMessage, requestMessage, presentationMessage] = await Promise.all([ - this.findProposalMessage(agentContext, proofRecordId), - this.findRequestMessage(agentContext, proofRecordId), - this.findPresentationMessage(agentContext, proofRecordId), - ]) - - // Create object with the keys and the message formats/attachments. We can then loop over this in a generic - // way so we don't have to add the same operation code four times - const messages = { - proposal: [proposalMessage?.formats, proposalMessage?.proposalsAttach], - request: [requestMessage?.formats, requestMessage?.requestPresentationsAttach], - presentation: [presentationMessage?.formats, presentationMessage?.presentationsAttach], - } as const - - const formatData: GetFormatDataReturn = {} - - // We loop through all of the message keys as defined above - for (const [messageKey, [formats, attachments]] of Object.entries(messages)) { - // Message can be undefined, so we continue if it is not defined - if (!formats || !attachments) continue - - // Find all format services associated with the message - const formatServices = this.getFormatServicesFromMessage(formats) - - const messageFormatData: FormatDataMessagePayload = {} - - // Loop through all of the format services, for each we will extract the attachment data and assign this to the object - // using the unique format key (e.g. indy) - for (const formatService of formatServices) { - const attachment = this.getAttachmentForService(formatService, formats, attachments) - messageFormatData[formatService.formatKey] = attachment.getDataAsJson() - } - - formatData[messageKey as Exclude] = - messageFormatData - } - - return formatData - } - - private getFormatServicesFromMessage(messageFormats: ProofFormatSpec[]): ProofFormatService[] { - const formatServices = new Set() - - for (const msg of messageFormats) { - const service = this.getFormatServiceForFormat(msg) - if (service) formatServices.add(service) - } - - return Array.from(formatServices) - } - - private getAttachmentForService( - proofFormatService: ProofFormatService, - formats: ProofFormatSpec[], - attachments: Attachment[] - ) { - const attachmentId = this.getAttachmentIdForService(proofFormatService, formats) - const attachment = attachments.find((attachment) => attachment.id === attachmentId) - - if (!attachment) { - throw new AriesFrameworkError(`Attachment with id ${attachmentId} not found in attachments.`) - } - - return attachment - } - - private getAttachmentIdForService(proofFormatService: ProofFormatService, formats: ProofFormatSpec[]) { - const format = formats.find((format) => proofFormatService.supportsFormat(format.format)) - - if (!format) throw new AriesFrameworkError(`No attachment found for service ${proofFormatService.formatKey}`) - - return format.attachmentId - } - - public async getRequestedCredentialsForProofRequest( - agentContext: AgentContext, - options: GetRequestedCredentialsForProofRequestOptions - ): Promise> { - const requestMessage = await this.didCommMessageRepository.findAgentMessage(agentContext, { - associatedRecordId: options.proofRecord.id, - messageClass: V2RequestPresentationMessage, - }) - - if (!requestMessage) { - throw new AriesFrameworkError('No proof request found.') - } - - const requestAttachments = requestMessage.getAttachmentFormats() - - let result = { - proofFormats: {}, - } - for (const attachmentFormat of requestAttachments) { - const service = this.getFormatServiceForFormat(attachmentFormat.format) - - if (!service) { - throw new AriesFrameworkError('No format service found for getting requested.') - } - - result = { - ...result, - ...(await service.getRequestedCredentialsForProofRequest(agentContext, { - attachment: attachmentFormat.attachment, - presentationProposal: undefined, - config: options.config, - })), - } - } - - return result - } - - public async autoSelectCredentialsForProofRequest( - options: FormatRetrievedCredentialOptions - ): Promise> { - let returnValue = { - proofFormats: {}, - } - - for (const [id] of Object.entries(options.proofFormats)) { - const service = this.formatServiceMap[id] - const credentials = await service.autoSelectCredentialsForProofRequest(options) - returnValue = { ...returnValue, ...credentials } - } - - return returnValue - } - - public registerMessageHandlers( - dispatcher: Dispatcher, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - routingService: RoutingService - ): void { - dispatcher.registerMessageHandler( - new V2ProposePresentationHandler(this, agentConfig, this.didCommMessageRepository, proofResponseCoordinator) - ) - - dispatcher.registerMessageHandler( - new V2RequestPresentationHandler( - this, - agentConfig, - proofResponseCoordinator, - mediationRecipientService, - this.didCommMessageRepository, - routingService - ) - ) - - dispatcher.registerMessageHandler( - new V2PresentationHandler(this, agentConfig, proofResponseCoordinator, this.didCommMessageRepository) - ) - dispatcher.registerMessageHandler(new V2PresentationAckHandler(this)) - dispatcher.registerMessageHandler(new V2PresentationProblemReportHandler(this)) - } - - private getFormatServiceForFormat(format: ProofFormatSpec) { - for (const service of Object.values(this.formatServiceMap)) { - if (service.supportsFormat(format.format)) { - return service - } - } - return null - } -} diff --git a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts similarity index 61% rename from packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts index 255c97a92b..397e0b8866 100644 --- a/packages/core/src/modules/proofs/__tests__/V2ProofService.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts @@ -1,44 +1,55 @@ -import type { AgentContext } from '../../../agent' -import type { Wallet } from '../../../wallet/Wallet' -import type { ProofStateChangedEvent } from '../ProofEvents' -import type { CustomProofTags } from '../repository/ProofExchangeRecord' +import type { ProofStateChangedEvent } from '../../../ProofEvents' +import type { CustomProofTags } from '../../../repository/ProofExchangeRecord' import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../tests/helpers' -import { EventEmitter } from '../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' -import { Attachment, AttachmentData } from '../../../decorators/attachment/Attachment' -import { DidCommMessageRepository } from '../../../storage' -import { ConnectionService, DidExchangeState } from '../../connections' -import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' -import { ProofEventTypes } from '../ProofEvents' -import { PresentationProblemReportReason } from '../errors/PresentationProblemReportReason' -import { V2_INDY_PRESENTATION, V2_INDY_PRESENTATION_REQUEST } from '../formats/ProofFormatConstants' -import { IndyProofFormatService } from '../formats/indy/IndyProofFormatService' -import { ProofState } from '../models/ProofState' -import { V2ProofService } from '../protocol/v2/V2ProofService' -import { V2PresentationProblemReportMessage, V2RequestPresentationMessage } from '../protocol/v2/messages' -import { ProofExchangeRecord } from '../repository/ProofExchangeRecord' -import { ProofRepository } from '../repository/ProofRepository' - -import { credDef } from './fixtures' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' +import { EventEmitter } from '../../../../../agent/EventEmitter' +import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' +import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' +import { DidCommMessageRepository } from '../../../../../storage' +import { uuid } from '../../../../../utils/uuid' +import { ConnectionService, DidExchangeState } from '../../../../connections' +import { ProofEventTypes } from '../../../ProofEvents' +import { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' +import { IndyProofFormatService } from '../../../formats/indy/IndyProofFormatService' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' +import { ProofState } from '../../../models/ProofState' +import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' +import { ProofRepository } from '../../../repository/ProofRepository' +import { V2ProofProtocol } from '../V2ProofProtocol' +import { V2PresentationProblemReportMessage, V2RequestPresentationMessage } from '../messages' // Mock classes -jest.mock('../repository/ProofRepository') -jest.mock('../../../modules/ledger/services/IndyLedgerService') -jest.mock('../../indy/services/IndyHolderService') -jest.mock('../../indy/services/IndyIssuerService') -jest.mock('../../indy/services/IndyVerifierService') -jest.mock('../../connections/services/ConnectionService') -jest.mock('../../../storage/Repository') +jest.mock('../../../repository/ProofRepository') +jest.mock('../../../../connections/services/ConnectionService') +jest.mock('../../../../../storage/Repository') // Mock typed object const ProofRepositoryMock = ProofRepository as jest.Mock -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock const connectionServiceMock = ConnectionService as jest.Mock const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const indyProofFormatServiceMock = IndyProofFormatService as jest.Mock +const IndyProofFormatServiceMock = IndyProofFormatService as jest.Mock + +const proofRepository = new ProofRepositoryMock() +const connectionService = new connectionServiceMock() +const didCommMessageRepository = new didCommMessageRepositoryMock() +const indyProofFormatService = new IndyProofFormatServiceMock() + +const agentConfig = getAgentConfig('V2ProofProtocolTest') +const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) + +const agentContext = getAgentContext({ + registerInstances: [ + [ProofRepository, proofRepository], + [DidCommMessageRepository, didCommMessageRepository], + [ConnectionService, connectionService], + [EventEmitter, eventEmitter], + ], + agentConfig, +}) + +const proofProtocol = new V2ProofProtocol({ proofFormats: [indyProofFormatService] }) const connection = getMockConnection({ id: '123', @@ -64,30 +75,16 @@ const mockProofExchangeRecord = ({ id, }: { state?: ProofState - requestMessage?: V2RequestPresentationMessage tags?: CustomProofTags threadId?: string connectionId?: string id?: string } = {}) => { - const requestPresentationMessage = new V2RequestPresentationMessage({ - attachmentInfo: [ - { - format: { - attachmentId: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', - format: V2_INDY_PRESENTATION, - }, - attachment: requestAttachment, - }, - ], - comment: 'some comment', - }) - const proofRecord = new ProofExchangeRecord({ protocolVersion: 'v2', id, state: state || ProofState.RequestSent, - threadId: threadId ?? requestPresentationMessage.id, + threadId: threadId ?? uuid(), connectionId: connectionId ?? '123', tags, }) @@ -95,57 +92,23 @@ const mockProofExchangeRecord = ({ return proofRecord } -describe('V2ProofService', () => { - let proofRepository: ProofRepository - let proofService: V2ProofService - let ledgerService: IndyLedgerService - let wallet: Wallet - let eventEmitter: EventEmitter - let connectionService: ConnectionService - let didCommMessageRepository: DidCommMessageRepository - let indyProofFormatService: IndyProofFormatService - let agentContext: AgentContext - - beforeEach(() => { - agentContext = getAgentContext() - const agentConfig = getAgentConfig('V2ProofServiceTest') - proofRepository = new ProofRepositoryMock() - ledgerService = new IndyLedgerServiceMock() - eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) - connectionService = new connectionServiceMock() - didCommMessageRepository = new didCommMessageRepositoryMock() - indyProofFormatService = new indyProofFormatServiceMock() - - proofService = new V2ProofService( - agentConfig, - connectionService, - proofRepository, - didCommMessageRepository, - eventEmitter, - indyProofFormatService, - wallet - ) - - mockFunction(ledgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) - }) - +describe('V2ProofProtocol', () => { describe('processProofRequest', () => { let presentationRequest: V2RequestPresentationMessage let messageContext: InboundMessageContext beforeEach(() => { presentationRequest = new V2RequestPresentationMessage({ - attachmentInfo: [ - { - format: { - attachmentId: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', - format: V2_INDY_PRESENTATION_REQUEST, - }, - attachment: requestAttachment, - }, + formats: [ + new ProofFormatSpec({ + attachmentId: 'abdc8b63-29c6-49ad-9e10-98f9d85db9a2', + format: 'hlindy/proof-req@v2.0', + }), ], + requestAttachments: [requestAttachment], comment: 'Proof Request', }) + messageContext = new InboundMessageContext(presentationRequest, { agentContext, connection }) }) @@ -153,7 +116,7 @@ describe('V2ProofService', () => { const repositorySaveSpy = jest.spyOn(proofRepository, 'save') // when - const returnedProofExchangeRecord = await proofService.processRequest(messageContext) + const returnedProofExchangeRecord = await proofProtocol.processRequest(messageContext) // then const expectedProofExchangeRecord = { @@ -175,7 +138,7 @@ describe('V2ProofService', () => { eventEmitter.on(ProofEventTypes.ProofStateChanged, eventListenerMock) // when - await proofService.processRequest(messageContext) + await proofProtocol.processRequest(messageContext) // then expect(eventListenerMock).toHaveBeenCalledWith({ @@ -254,7 +217,7 @@ describe('V2ProofService', () => { mockFunction(proofRepository.getSingleByQuery).mockReturnValue(Promise.resolve(proof)) // when - const returnedCredentialRecord = await proofService.processProblemReport(messageContext) + const returnedCredentialRecord = await proofProtocol.processProblemReport(messageContext) // then const expectedCredentialRecord = { diff --git a/packages/core/tests/v2-connectionless-proofs.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts similarity index 88% rename from packages/core/tests/v2-connectionless-proofs.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts index 921f6c3127..f924869f3f 100644 --- a/packages/core/tests/v2-connectionless-proofs.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -1,36 +1,29 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { ProofStateChangedEvent } from '../src/modules/proofs' +import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { ProofStateChangedEvent } from '../../../ProofEvents' import { Subject, ReplaySubject } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { V1CredentialPreview } from '../src' -import { Agent } from '../src/agent/Agent' -import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' -import { HandshakeProtocol } from '../src/modules/connections/models/HandshakeProtocol' +import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' import { - PredicateType, - ProofState, - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - AutoAcceptProof, - ProofEventTypes, -} from '../src/modules/proofs' -import { MediatorPickupStrategy } from '../src/modules/routing/MediatorPickupStrategy' -import { LinkedAttachment } from '../src/utils/LinkedAttachment' -import { uuid } from '../src/utils/uuid' - -import { - getAgentOptions, - issueCredential, - makeConnection, - prepareForIssuance, setupProofsTest, waitForProofExchangeRecordSubject, -} from './helpers' -import testLogger from './logger' + getAgentOptions, + prepareForIssuance, + makeConnection, + issueCredential, +} from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { Agent } from '../../../../../agent/Agent' +import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' +import { LinkedAttachment } from '../../../../../utils/LinkedAttachment' +import { uuid } from '../../../../../utils/uuid' +import { HandshakeProtocol } from '../../../../connections' +import { V1CredentialPreview } from '../../../../credentials' +import { MediatorPickupStrategy } from '../../../../routing' +import { ProofEventTypes } from '../../../ProofEvents' +import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' +import { AutoAcceptProof, ProofState } from '../../../models' describe('Present Proof', () => { let agents: Agent[] @@ -44,8 +37,8 @@ describe('Present Proof', () => { test('Faber starts with connection-less proof requests to Alice', async () => { const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs', - 'Alice connection-less Proofs', + 'Faber connection-less Proofs v2', + 'Alice connection-less Proofs v2', AutoAcceptProof.Never ) agents = [aliceAgent, faberAgent] @@ -86,7 +79,6 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - nonce: '12345678901', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -106,11 +98,8 @@ describe('Present Proof', () => { testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { @@ -135,7 +124,7 @@ describe('Present Proof', () => { }) // Faber accepts presentation - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits till it receives presentation ack aliceProofExchangeRecord = await aliceProofExchangeRecordPromise @@ -191,7 +180,6 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - nonce: '12345678901', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -260,7 +248,6 @@ describe('Present Proof', () => { const aliceOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { autoAcceptProofs: AutoAcceptProof.Always, - // logger: new TestLogger(LogLevel.test), mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ domain: 'https://example.com', }), @@ -356,7 +343,6 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - nonce: '12345678901', requestedAttributes: attributes, requestedPredicates: predicates, }, diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts similarity index 84% rename from packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts index a337eca475..16a4808612 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-negotiation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts @@ -1,29 +1,28 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions, NegotiateProposalOptions } from '../../../ProofsApiOptions' +import type { AcceptProofProposalOptions, NegotiateProofProposalOptions } from '../../../ProofsApiOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' +import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage/didcomm' import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' import { AttributeFilter } from '../../../formats/indy/models/AttributeFilter' import { PredicateType } from '../../../formats/indy/models/PredicateType' import { ProofAttributeInfo } from '../../../formats/indy/models/ProofAttributeInfo' import { ProofPredicateInfo } from '../../../formats/indy/models/ProofPredicateInfo' import { ProofRequest } from '../../../formats/indy/models/ProofRequest' import { ProofState } from '../../../models/ProofState' -import { V2ProposalPresentationMessage, V2RequestPresentationMessage } from '../messages' +import { V2ProposePresentationMessage, V2RequestPresentationMessage } from '../messages' describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent let credDefId: CredDefId let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository @@ -57,7 +56,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', attributes: presentationPreview.attributes.filter((attribute) => attribute.name !== 'name'), predicates: presentationPreview.predicates, @@ -73,7 +71,7 @@ describe('Present Proof', () => { let proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, + messageClass: V2ProposePresentationMessage, }) expect(proposal).toMatchObject({ @@ -81,10 +79,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -97,7 +95,7 @@ describe('Present Proof', () => { comment: 'V2 propose proof test 1', }) // eslint-disable-next-line @typescript-eslint/no-explicit-any - let proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any + let proposalAttach = proposal?.proposalAttachments[0].getDataAsJson() as any let attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] let predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] expect(proposalAttach).toMatchObject({ @@ -164,27 +162,24 @@ describe('Present Proof', () => { }), } - const requestProofAsResponseOptions: NegotiateProposalOptions = { + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) + + testLogger.test('Faber sends new proof request to Alice') + faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal({ proofRecordId: faberProofExchangeRecord.id, proofFormats: { indy: { name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', requestedAttributes: attributes, requestedPredicates: predicates, }, }, - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, }) - testLogger.test('Faber sends new proof request to Alice') - faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal(requestProofAsResponseOptions) - testLogger.test('Alice waits for proof request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise @@ -198,7 +193,7 @@ describe('Present Proof', () => { expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', id: expect.any(String), - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -229,7 +224,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', attributes: presentationPreview.attributes.filter((attribute) => attribute.name === 'name'), predicates: presentationPreview.predicates, @@ -245,7 +239,7 @@ describe('Present Proof', () => { proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, + messageClass: V2ProposePresentationMessage, }) expect(proposal).toMatchObject({ @@ -253,10 +247,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -269,7 +263,7 @@ describe('Present Proof', () => { comment: 'V2 propose proof test 2', }) // eslint-disable-next-line @typescript-eslint/no-explicit-any - proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any + proposalAttach = proposal?.proposalAttachments[0].getDataAsJson() as any attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] expect(proposalAttach).toMatchObject({ @@ -331,10 +325,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -355,17 +349,17 @@ describe('Present Proof', () => { protocolVersion: 'v2', }) - const presentationProposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) + const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) - expect(presentationProposalMessage).toMatchObject({ + expect(proposalMessage).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -379,7 +373,7 @@ describe('Present Proof', () => { }) // eslint-disable-next-line @typescript-eslint/no-explicit-any - proposalAttach = proposal?.proposalsAttach[0].getDataAsJson() as any + proposalAttach = proposal?.proposalAttachments[0].getDataAsJson() as any attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] expect(proposalAttach).toMatchObject({ @@ -412,33 +406,37 @@ describe('Present Proof', () => { )) as V2RequestPresentationMessage const proofRequest = JsonTransformer.fromJSON( - proofRequestMessage.requestPresentationsAttach[0].getDataAsJson(), + proofRequestMessage.requestAttachments[0].getDataAsJson(), ProofRequest ) const predicateKey = proofRequest.requestedPredicates?.keys().next().value - const predicate = Object.values(predicates)[0] - expect(proofRequest).toMatchObject({ + expect(proofRequest.toJSON()).toMatchObject({ name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', + nonce: expect.any(String), version: '1.0', - requestedAttributes: new Map( - Object.entries({ - '0': new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - }) - ), - requestedPredicates: new Map( - Object.entries({ - [predicateKey]: predicate, - }) - ), + requested_attributes: { + '0': { + name: 'name', + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, + requested_predicates: { + [predicateKey]: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credDefId, + }, + ], + }, + }, }) }) }) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts similarity index 89% rename from packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts index a8f2d6a531..2fccef1f98 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-presentation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts @@ -1,26 +1,21 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' -import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' +import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' -import { - V2_INDY_PRESENTATION_PROPOSAL, - V2_INDY_PRESENTATION_REQUEST, - V2_INDY_PRESENTATION, -} from '../../../formats/ProofFormatConstants' import { ProofState } from '../../../models/ProofState' import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' -import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' +import { V2ProposePresentationMessage } from '../messages/V2ProposePresentationMessage' describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository @@ -28,8 +23,8 @@ describe('Present Proof', () => { beforeAll(async () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' + 'Faber agent v2 present proof', + 'Alice agent v2 present proof' )) }) @@ -54,7 +49,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'ProofRequest', - nonce: '947121108704767252195126', version: '1.0', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, @@ -70,7 +64,7 @@ describe('Present Proof', () => { const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, + messageClass: V2ProposePresentationMessage, }) expect(proposal).toMatchObject({ @@ -78,10 +72,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -130,10 +124,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -159,11 +153,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -190,10 +181,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION, + format: 'hlindy/proof@v2.0', }, ], - presentationsAttach: [ + presentationAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -223,7 +214,7 @@ describe('Present Proof', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-proposal.e2e.test.ts similarity index 84% rename from packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-proposal.e2e.test.ts index 39a29df125..68c09d5717 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-proposal.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-proposal.e2e.test.ts @@ -1,28 +1,27 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' import type { ProofExchangeRecord } from '../../../repository' -import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' +import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' -import { V2_INDY_PRESENTATION_PROPOSAL } from '../../../formats/ProofFormatConstants' import { ProofState } from '../../../models/ProofState' -import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' +import { V2ProposePresentationMessage } from '../messages/V2ProposePresentationMessage' describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview let faberPresentationRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository beforeAll(async () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' + 'Faber agent v2', + 'Alice agent v2' )) }) @@ -47,7 +46,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, @@ -63,7 +61,7 @@ describe('Present Proof', () => { const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { associatedRecordId: faberPresentationRecord.id, - messageClass: V2ProposalPresentationMessage, + messageClass: V2ProposePresentationMessage, }) expect(proposal).toMatchObject({ @@ -71,10 +69,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts similarity index 87% rename from packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts index 47a697821f..827555ec65 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/indy-proof-request.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts @@ -2,21 +2,20 @@ import type { Agent } from '../../../../../agent/Agent' import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { PresentationPreview } from '../../v1/models/V1PresentationPreview' +import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' -import { V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST } from '../../../formats/ProofFormatConstants' import { ProofState } from '../../../models/ProofState' import { V2RequestPresentationMessage } from '../messages' -import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' +import { V2ProposePresentationMessage } from '../messages/V2ProposePresentationMessage' describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord let didCommMessageRepository: DidCommMessageRepository @@ -24,8 +23,8 @@ describe('Present Proof', () => { beforeAll(async () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' + 'Faber agent v2', + 'Alice agent v2' )) }) @@ -50,7 +49,6 @@ describe('Present Proof', () => { proofFormats: { indy: { name: 'ProofRequest', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, @@ -66,7 +64,7 @@ describe('Present Proof', () => { const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, + messageClass: V2ProposePresentationMessage, }) expect(proposal).toMatchObject({ @@ -74,10 +72,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -97,7 +95,7 @@ describe('Present Proof', () => { }) }) - test(`Faber accepts the Proposal send by Alice`, async () => { + test(`Faber accepts the Proposal sent by Alice`, async () => { // Accept Proposal const acceptProposalOptions: AcceptProofProposalOptions = { proofRecordId: faberProofExchangeRecord.id, @@ -126,10 +124,10 @@ describe('Present Proof', () => { formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', diff --git a/packages/core/tests/v2-proofs-auto-accept.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts similarity index 79% rename from packages/core/tests/v2-proofs-auto-accept.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts index 0ab3d81e13..e3a9841613 100644 --- a/packages/core/tests/v2-proofs-auto-accept.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts @@ -1,17 +1,11 @@ -import type { Agent, ConnectionRecord } from '../src' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections' +import type { V1PresentationPreview } from '../../v1' -import { - AutoAcceptProof, - ProofState, - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - PredicateType, -} from '../src' - -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' -import testLogger from './logger' +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' +import { AutoAcceptProof, ProofState } from '../../../models' describe('Auto accept present proof', () => { let faberAgent: Agent @@ -19,7 +13,7 @@ describe('Auto accept present proof', () => { let credDefId: string let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview describe('Auto accept on `always`', () => { beforeAll(async () => { @@ -45,7 +39,6 @@ describe('Auto accept present proof', () => { protocolVersion: 'v2', proofFormats: { indy: { - nonce: '1298236324864', name: 'abc', version: '1.0', attributes: presentationPreview.attributes, @@ -94,7 +87,6 @@ describe('Auto accept present proof', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -110,7 +102,7 @@ describe('Auto accept present proof', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + describe("Auto accept on 'contentApproved'", () => { beforeAll(async () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = @@ -128,15 +120,18 @@ describe('Auto accept present proof', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `contentApproved`', async () => { + test("Alice starts with proof proposal to Faber, both with autoAcceptProof on 'contentApproved'", async () => { testLogger.test('Alice sends presentation proposal to Faber') + const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + await aliceAgent.proofs.proposeProof({ connectionId: aliceConnection.id, protocolVersion: 'v2', proofFormats: { indy: { - nonce: '1298236324864', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, name: 'abc', @@ -145,20 +140,18 @@ describe('Auto accept present proof', () => { }, }) - const { id: proofRecordId } = await waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, + const faberProofExchangeRecord = await faberProofExchangeRecordPromise + await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, }) - testLogger.test('Faber accepts presentation proposal from Alice') - await faberAgent.proofs.acceptProposal({ proofRecordId }) - await Promise.all([ waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), ]) }) - test('Faber starts with proof requests to Alice, both with autoAcceptProof on `contentApproved`', async () => { + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'contentApproved'", async () => { testLogger.test('Faber sends presentation request to Alice') const attributes = { name: new ProofAttributeInfo({ @@ -183,6 +176,10 @@ describe('Auto accept present proof', () => { }), } + const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + await faberAgent.proofs.requestProof({ protocolVersion: 'v2', connectionId: faberConnection.id, @@ -190,19 +187,16 @@ describe('Auto accept present proof', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324866', requestedAttributes: attributes, requestedPredicates: predicates, }, }, }) - testLogger.test('Alice waits for request from Faber') - const { id: proofRecordId } = await waitForProofExchangeRecord(aliceAgent, { - state: ProofState.RequestReceived, + const aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofExchangeRecord.id, }) - const { proofFormats } = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ proofRecordId }) - await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) await Promise.all([ waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), diff --git a/packages/core/tests/v2-indy-proofs.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts similarity index 77% rename from packages/core/tests/v2-indy-proofs.test.ts rename to packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts index f54f6da15e..815188bf69 100644 --- a/packages/core/tests/v2-indy-proofs.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts @@ -1,47 +1,29 @@ -import type { Agent, ConnectionRecord } from '../src' -import type { AcceptProofProposalOptions } from '../src/modules/proofs/ProofsApiOptions' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' - -import { - ProofExchangeRecord, - AttributeFilter, - PredicateType, - ProofAttributeInfo, - ProofPredicateInfo, - ProofState, -} from '../src' -import { getGroupKeysFromIndyProofFormatData } from '../src/modules/proofs/__tests__/groupKeys' -import { - V2_INDY_PRESENTATION_PROPOSAL, - V2_INDY_PRESENTATION_REQUEST, - V2_INDY_PRESENTATION, -} from '../src/modules/proofs/formats/ProofFormatConstants' -import { - V2PresentationMessage, - V2ProposalPresentationMessage, - V2RequestPresentationMessage, -} from '../src/modules/proofs/protocol/v2/messages' -import { DidCommMessageRepository } from '../src/storage/didcomm' - -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' -import testLogger from './logger' +import type { Agent } from '../../../../../agent/Agent' +import type { ConnectionRecord } from '../../../../connections' +import type { V1PresentationPreview } from '../../v1' + +import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import testLogger from '../../../../../../tests/logger' +import { getGroupKeysFromIndyProofFormatData } from '../../../formats/indy/__tests__/groupKeys' +import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' +import { ProofState } from '../../../models' +import { ProofExchangeRecord } from '../../../repository' +import { V2ProposePresentationMessage, V2RequestPresentationMessage, V2PresentationMessage } from '../messages' describe('Present Proof', () => { let faberAgent: Agent let aliceAgent: Agent - let credDefId: CredDefId + let credDefId: string let aliceConnection: ConnectionRecord let faberConnection: ConnectionRecord let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: PresentationPreview - let didCommMessageRepository: DidCommMessageRepository + let presentationPreview: V1PresentationPreview beforeAll(async () => { testLogger.test('Initializing the agents') ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent', 'Alice agent')) + await setupProofsTest('Faber agent indy proofs', 'Alice agent indy proofs')) }) afterAll(async () => { @@ -67,7 +49,6 @@ describe('Present Proof', () => { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, }, @@ -78,22 +59,16 @@ describe('Present Proof', () => { testLogger.test('Faber waits for a presentation proposal from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_PROPOSAL, + format: 'hlindy/proof-req@v2.0', }, ], - proposalsAttach: [ + proposalAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -111,36 +86,30 @@ describe('Present Proof', () => { protocolVersion: 'v2', }) - const acceptProposalOptions: AcceptProofProposalOptions = { - proofRecordId: faberProofExchangeRecord.id, - } - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber accepts the presentation proposal from Alice testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ + proofRecordId: faberProofExchangeRecord.id, + }) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -158,11 +127,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -179,20 +145,16 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION, + format: 'hlindy/proof@v2.0', }, ], - presentationsAttach: [ + presentationAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -220,7 +182,7 @@ describe('Present Proof', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') @@ -249,7 +211,7 @@ describe('Present Proof', () => { const requestMessage = await aliceAgent.proofs.findRequestMessage(aliceProofExchangeRecord.id) const presentationMessage = await aliceAgent.proofs.findPresentationMessage(aliceProofExchangeRecord.id) - expect(proposalMessage).toBeInstanceOf(V2ProposalPresentationMessage) + expect(proposalMessage).toBeInstanceOf(V2ProposePresentationMessage) expect(requestMessage).toBeInstanceOf(V2RequestPresentationMessage) expect(presentationMessage).toBeInstanceOf(V2PresentationMessage) @@ -263,7 +225,7 @@ describe('Present Proof', () => { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', + nonce: expect.any(String), requested_attributes: { 0: { name: 'name', @@ -295,7 +257,7 @@ describe('Present Proof', () => { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', + nonce: expect.any(String), requested_attributes: { 0: { name: 'name', @@ -389,6 +351,8 @@ describe('Present Proof', () => { connectionId: faberConnection.id, proofFormats: { indy: { + name: 'Proof Request', + version: '1.0.0', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -399,20 +363,16 @@ describe('Present Proof', () => { testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -434,11 +394,8 @@ describe('Present Proof', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -455,20 +412,16 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION, + format: 'hlindy/proof@v2.0', }, ], - presentationsAttach: [ + presentationAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -496,7 +449,7 @@ describe('Present Proof', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') @@ -567,9 +520,8 @@ describe('Present Proof', () => { connectionId: faberConnection.id, proofFormats: { indy: { - name: 'proof-request', - version: '1.0', - nonce: '1298236324864', + name: 'Proof Request', + version: '1.0.0', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -580,18 +532,76 @@ describe('Present Proof', () => { testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const retrievedCredentials = await faberAgent.proofs.getRequestedCredentialsForProofRequest({ - proofRecordId: faberProofExchangeRecord.id, - config: {}, + const retrievedCredentials = await aliceAgent.proofs.getCredentialsForRequest({ + proofRecordId: aliceProofExchangeRecord.id, }) - if (retrievedCredentials.proofFormats.indy) { - const keys = Object.keys(retrievedCredentials.proofFormats.indy?.requestedAttributes) - expect(keys).toContain('name') - expect(keys).toContain('image_0') - } else { - fail() - } + expect(retrievedCredentials).toMatchObject({ + proofFormats: { + indy: { + attributes: { + name: [ + { + credentialId: expect.any(String), + revealed: true, + credentialInfo: { + referent: expect.any(String), + attributes: { + image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', + image_1: 'hl:zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', + name: 'John', + age: '99', + }, + schemaId: expect.any(String), + credentialDefinitionId: expect.any(String), + revocationRegistryId: null, + credentialRevocationId: null, + }, + }, + ], + image_0: [ + { + credentialId: expect.any(String), + revealed: true, + credentialInfo: { + referent: expect.any(String), + attributes: { + age: '99', + image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', + image_1: 'hl:zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', + name: 'John', + }, + schemaId: expect.any(String), + credentialDefinitionId: expect.any(String), + revocationRegistryId: null, + credentialRevocationId: null, + }, + }, + ], + }, + predicates: { + age: [ + { + credentialId: expect.any(String), + credentialInfo: { + referent: expect.any(String), + attributes: { + image_1: 'hl:zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', + image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', + name: 'John', + age: '99', + }, + schemaId: expect.any(String), + credentialDefinitionId: expect.any(String), + revocationRegistryId: null, + credentialRevocationId: null, + }, + }, + ], + }, + }, + }, + }) }) test('Faber starts with proof request to Alice but gets Problem Reported', async () => { @@ -641,7 +651,6 @@ describe('Present Proof', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -652,20 +661,17 @@ describe('Present Proof', () => { testLogger.test('Alice waits for presentation request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, - }) + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ { attachmentId: expect.any(String), - format: V2_INDY_PRESENTATION_REQUEST, + format: 'hlindy/proof-req@v2.0', }, ], - requestPresentationsAttach: [ + requestAttachments: [ { id: expect.any(String), mimeType: 'application/json', @@ -689,10 +695,10 @@ describe('Present Proof', () => { state: ProofState.Abandoned, }) - aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport( - aliceProofExchangeRecord.id, - 'Problem inside proof request' - ) + aliceProofExchangeRecord = await aliceAgent.proofs.sendProblemReport({ + description: 'Problem inside proof request', + proofRecordId: aliceProofExchangeRecord.id, + }) faberProofExchangeRecord = await faberProofExchangeRecordPromise diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts index 3d28970a6e..43b9e15a69 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationAckHandler.ts @@ -1,17 +1,17 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { ProofService } from '../../../ProofService' +import type { ProofProtocol } from '../../ProofProtocol' import { V2PresentationAckMessage } from '../messages' export class V2PresentationAckHandler implements MessageHandler { - private proofService: ProofService + private proofProtocol: ProofProtocol public supportedMessages = [V2PresentationAckMessage] - public constructor(proofService: ProofService) { - this.proofService = proofService + public constructor(proofProtocol: ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - await this.proofService.processAck(messageContext) + await this.proofProtocol.processAck(messageContext) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts index 9f2d074d67..e3c68c1d84 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationHandler.ts @@ -1,75 +1,56 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' import type { ProofExchangeRecord } from '../../../repository' -import type { V2ProofService } from '../V2ProofService' +import type { V2ProofProtocol } from '../V2ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' +import { DidCommMessageRepository } from '../../../../../storage' import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' export class V2PresentationHandler implements MessageHandler { - private proofService: V2ProofService - private agentConfig: AgentConfig - private proofResponseCoordinator: ProofResponseCoordinator - private didCommMessageRepository: DidCommMessageRepository + private proofProtocol: V2ProofProtocol public supportedMessages = [V2PresentationMessage] - public constructor( - proofService: V2ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - didCommMessageRepository: DidCommMessageRepository - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.didCommMessageRepository = didCommMessageRepository + public constructor(proofProtocol: V2ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processPresentation(messageContext) + const proofRecord = await this.proofProtocol.processPresentation(messageContext) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToPresentation( - messageContext.agentContext, - proofRecord - ) + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToPresentation(messageContext.agentContext, { + proofRecord, + presentationMessage: messageContext.message, + }) if (shouldAutoRespond) { - return await this.createAck(proofRecord, messageContext) + return await this.acceptPresentation(proofRecord, messageContext) } } - private async createAck( - record: ProofExchangeRecord, + private async acceptPresentation( + proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending acknowledgement with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) + messageContext.agentContext.config.logger.info(`Automatically sending acknowledgement with autoAccept`) - const { message, proofRecord } = await this.proofService.createAck(messageContext.agentContext, { - proofRecord: record, + const { message } = await this.proofProtocol.acceptPresentation(messageContext.agentContext, { + proofRecord, }) - const requestMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + const requestMessage = await didCommMessageRepository.findAgentMessage(messageContext.agentContext, { associatedRecordId: proofRecord.id, messageClass: V2RequestPresentationMessage, }) - const presentationMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2PresentationMessage, - }) - if (messageContext.connection) { return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, connection: messageContext.connection, associatedRecord: proofRecord, }) - } else if (requestMessage?.service && presentationMessage?.service) { - const recipientService = presentationMessage?.service + } else if (requestMessage?.service && messageContext.message?.service) { + const recipientService = messageContext.message?.service const ourService = requestMessage?.service return new OutboundMessageContext(message, { @@ -81,6 +62,6 @@ export class V2PresentationHandler implements MessageHandler { }) } - this.agentConfig.logger.error(`Could not automatically create presentation ack`) + messageContext.agentContext.config.logger.error(`Could not automatically create presentation ack`) } } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts index 947a8c6c44..5d9512d824 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2PresentationProblemReportHandler.ts @@ -1,13 +1,13 @@ import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { V2ProofService } from '../V2ProofService' +import type { V2ProofProtocol } from '../V2ProofProtocol' import { V2PresentationProblemReportMessage } from '../messages' export class V2PresentationProblemReportHandler implements MessageHandler { - private proofService: V2ProofService + private proofService: V2ProofProtocol public supportedMessages = [V2PresentationProblemReportMessage] - public constructor(proofService: V2ProofService) { + public constructor(proofService: V2ProofProtocol) { this.proofService = proofService } diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts index 9432a3ca56..589ff6db3e 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2ProposePresentationHandler.ts @@ -1,99 +1,42 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { ProofFormat } from '../../../formats/ProofFormat' -import type { - CreateProofRequestFromProposalOptions, - CreateRequestAsResponseOptions, - ProofRequestFromProposalOptions, -} from '../../../models/ProofServiceOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V2ProofService } from '../V2ProofService' +import type { V2ProofProtocol } from '../V2ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { V2ProposalPresentationMessage } from '../messages/V2ProposalPresentationMessage' +import { V2ProposePresentationMessage } from '../messages/V2ProposePresentationMessage' -export class V2ProposePresentationHandler implements MessageHandler { - private proofService: V2ProofService - private agentConfig: AgentConfig - private didCommMessageRepository: DidCommMessageRepository - private proofResponseCoordinator: ProofResponseCoordinator - public supportedMessages = [V2ProposalPresentationMessage] +export class V2ProposePresentationHandler implements MessageHandler { + private proofProtocol: V2ProofProtocol + public supportedMessages = [V2ProposePresentationMessage] - public constructor( - proofService: V2ProofService, - agentConfig: AgentConfig, - didCommMessageRepository: DidCommMessageRepository, - proofResponseCoordinator: ProofResponseCoordinator - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.didCommMessageRepository = didCommMessageRepository - this.proofResponseCoordinator = proofResponseCoordinator + public constructor(proofProtocol: V2ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processProposal(messageContext) + const proofRecord = await this.proofProtocol.processProposal(messageContext) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToProposal( - messageContext.agentContext, - proofRecord - ) + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToProposal(messageContext.agentContext, { + proofRecord, + proposalMessage: messageContext.message, + }) if (shouldAutoRespond) { - return this.createRequest(proofRecord, messageContext) + return this.acceptProposal(proofRecord, messageContext) } } - - private async createRequest( + private async acceptProposal( proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - this.agentConfig.logger.info( - `Automatically sending request with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) + messageContext.agentContext.config.logger.info(`Automatically sending request with autoAccept`) if (!messageContext.connection) { - this.agentConfig.logger.error('No connection on the messageContext') - throw new AriesFrameworkError('No connection on the messageContext') - } - - const proposalMessage = await this.didCommMessageRepository.findAgentMessage(messageContext.agentContext, { - associatedRecordId: proofRecord.id, - messageClass: V2ProposalPresentationMessage, - }) - - if (!proposalMessage) { - this.agentConfig.logger.error(`Proof record with id ${proofRecord.id} is missing required credential proposal`) - throw new AriesFrameworkError(`Proof record with id ${proofRecord.id} is missing required credential proposal`) - } - - const proofRequestFromProposalOptions: CreateProofRequestFromProposalOptions = { - proofRecord, - } - - const proofRequest: ProofRequestFromProposalOptions = await this.proofService.createProofRequestFromProposal( - messageContext.agentContext, - proofRequestFromProposalOptions - ) - - const indyProofRequest = proofRequest.proofFormats - - if (!indyProofRequest) { - this.agentConfig.logger.error('Failed to create proof request') - throw new AriesFrameworkError('Failed to create proof request.') - } - - const options: CreateRequestAsResponseOptions = { - proofRecord: proofRecord, - autoAcceptProof: proofRecord.autoAcceptProof, - proofFormats: indyProofRequest, - willConfirm: true, + messageContext.agentContext.config.logger.error('No connection on the messageContext, aborting auto accept') + return } - const { message } = await this.proofService.createRequestAsResponse(messageContext.agentContext, options) + const { message } = await this.proofProtocol.acceptProposal(messageContext.agentContext, { proofRecord }) return new OutboundMessageContext(message, { agentContext: messageContext.agentContext, diff --git a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts index 65edcd85c5..e43a60df7e 100644 --- a/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts +++ b/packages/core/src/modules/proofs/protocol/v2/handlers/V2RequestPresentationHandler.ts @@ -1,86 +1,45 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' -import type { MediationRecipientService, RoutingService } from '../../../../routing' -import type { ProofResponseCoordinator } from '../../../ProofResponseCoordinator' -import type { ProofFormat } from '../../../formats/ProofFormat' -import type { - FormatRequestedCredentialReturn, - FormatRetrievedCredentialOptions, -} from '../../../models/ProofServiceOptions' import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V2ProofService } from '../V2ProofService' +import type { V2ProofProtocol } from '../V2ProofProtocol' import { OutboundMessageContext } from '../../../../../agent/models' import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' import { DidCommMessageRole } from '../../../../../storage' +import { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' +import { RoutingService } from '../../../../routing' import { V2RequestPresentationMessage } from '../messages/V2RequestPresentationMessage' -export class V2RequestPresentationHandler implements MessageHandler { - private proofService: V2ProofService - private agentConfig: AgentConfig - private proofResponseCoordinator: ProofResponseCoordinator - private mediationRecipientService: MediationRecipientService - private didCommMessageRepository: DidCommMessageRepository - private routingService: RoutingService +export class V2RequestPresentationHandler implements MessageHandler { + private proofProtocol: V2ProofProtocol public supportedMessages = [V2RequestPresentationMessage] - public constructor( - proofService: V2ProofService, - agentConfig: AgentConfig, - proofResponseCoordinator: ProofResponseCoordinator, - mediationRecipientService: MediationRecipientService, - didCommMessageRepository: DidCommMessageRepository, - routingService: RoutingService - ) { - this.proofService = proofService - this.agentConfig = agentConfig - this.proofResponseCoordinator = proofResponseCoordinator - this.mediationRecipientService = mediationRecipientService - this.didCommMessageRepository = didCommMessageRepository - this.routingService = routingService + public constructor(proofProtocol: V2ProofProtocol) { + this.proofProtocol = proofProtocol } public async handle(messageContext: MessageHandlerInboundMessage) { - const proofRecord = await this.proofService.processRequest(messageContext) + const proofRecord = await this.proofProtocol.processRequest(messageContext) + + const shouldAutoRespond = await this.proofProtocol.shouldAutoRespondToRequest(messageContext.agentContext, { + proofRecord, + requestMessage: messageContext.message, + }) - const shouldAutoRespond = await this.proofResponseCoordinator.shouldAutoRespondToRequest( - messageContext.agentContext, - proofRecord - ) + messageContext.agentContext.config.logger.debug(`Should auto respond to request: ${shouldAutoRespond}`) if (shouldAutoRespond) { - return await this.createPresentation(proofRecord, messageContext) + return await this.acceptRequest(proofRecord, messageContext) } } - private async createPresentation( - record: ProofExchangeRecord, + private async acceptRequest( + proofRecord: ProofExchangeRecord, messageContext: MessageHandlerInboundMessage ) { - const requestMessage = await this.didCommMessageRepository.getAgentMessage(messageContext.agentContext, { - associatedRecordId: record.id, - messageClass: V2RequestPresentationMessage, - }) - - this.agentConfig.logger.info( - `Automatically sending presentation with autoAccept on ${this.agentConfig.autoAcceptProofs}` - ) + messageContext.agentContext.config.logger.info(`Automatically sending presentation with autoAccept`) - const retrievedCredentials: FormatRetrievedCredentialOptions = - await this.proofService.getRequestedCredentialsForProofRequest(messageContext.agentContext, { - proofRecord: record, - config: { - filterByPresentationPreview: false, - }, - }) - - const requestedCredentials: FormatRequestedCredentialReturn = - await this.proofService.autoSelectCredentialsForProofRequest(retrievedCredentials) - - const { message, proofRecord } = await this.proofService.createPresentation(messageContext.agentContext, { - proofRecord: record, - proofFormats: requestedCredentials.proofFormats, + const { message } = await this.proofProtocol.acceptRequest(messageContext.agentContext, { + proofRecord, }) if (messageContext.connection) { @@ -89,16 +48,19 @@ export class V2RequestPresentationHandler(RoutingService) + const didCommMessageRepository = messageContext.agentContext.dependencyManager.resolve(DidCommMessageRepository) + + const routing = await routingService.getRouting(messageContext.agentContext) message.service = new ServiceDecorator({ serviceEndpoint: routing.endpoints[0], recipientKeys: [routing.recipientKey.publicKeyBase58], routingKeys: routing.routingKeys.map((key) => key.publicKeyBase58), }) - const recipientService = requestMessage.service + const recipientService = messageContext.message.service - await this.didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { + await didCommMessageRepository.saveOrUpdateAgentMessage(messageContext.agentContext, { agentMessage: message, associatedRecordId: proofRecord.id, role: DidCommMessageRole.Sender, @@ -113,6 +75,6 @@ export class V2RequestPresentationHandler { - const attachment = this.presentationsAttach.find((attachment) => attachment.id === format.attachmentId) - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) - } - - attachmentFormats.push({ format, attachment }) - }) - return attachmentFormats - } - @IsValidMessageType(V2PresentationMessage.type) public readonly type = V2PresentationMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/presentation') @@ -89,5 +62,9 @@ export class V2PresentationMessage extends AgentMessage { @IsArray() @ValidateNested({ each: true }) @IsInstance(Attachment, { each: true }) - public presentationsAttach!: Attachment[] + public presentationAttachments!: Attachment[] + + public getPresentationAttachmentById(id: string): Attachment | undefined { + return this.presentationAttachments.find((attachment) => attachment.id == id) + } } diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts index b36a69a7fe..ed97f72319 100644 --- a/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2PresentationProblemReportMessage.ts @@ -1,22 +1,10 @@ -import type { ProblemReportMessageOptions } from '../../../../problem-reports/messages/ProblemReportMessage' - import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' import { ProblemReportMessage } from '../../../../problem-reports/messages/ProblemReportMessage' -export type V2PresentationProblemReportMessageOptions = ProblemReportMessageOptions - /** * @see https://github.com/hyperledger/aries-rfcs/blob/main/features/0035-report-problem/README.md */ export class V2PresentationProblemReportMessage extends ProblemReportMessage { - /** - * Create new PresentationProblemReportMessage instance. - * @param options - */ - public constructor(options: V2PresentationProblemReportMessageOptions) { - super(options) - } - @IsValidMessageType(V2PresentationProblemReportMessage.type) public readonly type = V2PresentationProblemReportMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/problem-report') diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts deleted file mode 100644 index 265ed8ae7e..0000000000 --- a/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposalPresentationMessage.ts +++ /dev/null @@ -1,100 +0,0 @@ -import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' - -import { Expose, Type } from 'class-transformer' -import { IsArray, IsBoolean, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { AgentMessage } from '../../../../../agent/AgentMessage' -import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { uuid } from '../../../../../utils/uuid' -import { ProofFormatSpec } from '../../../models/ProofFormatSpec' - -export interface V2ProposePresentationMessageOptions { - id?: string - comment?: string - goalCode?: string - willConfirm?: boolean - parentThreadId?: string - attachmentInfo: ProofAttachmentFormat[] -} - -export class V2ProposalPresentationMessage extends AgentMessage { - public constructor(options: V2ProposePresentationMessageOptions) { - super() - - if (options) { - this.formats = [] - this.proposalsAttach = [] - this.id = options.id ?? uuid() - this.comment = options.comment - this.goalCode = options.goalCode - this.willConfirm = options.willConfirm ?? false - - if (options.parentThreadId) { - this.setThread({ - parentThreadId: options.parentThreadId, - }) - } - - for (const entry of options.attachmentInfo) { - this.addProposalsAttachment(entry) - } - } - } - - public addProposalsAttachment(attachment: ProofAttachmentFormat) { - this.formats.push(attachment.format) - this.proposalsAttach.push(attachment.attachment) - } - - /** - * Every attachment has a corresponding entry in the formats array. - * This method pairs those together in a {@link ProofAttachmentFormat} object. - */ - public getAttachmentFormats(): ProofAttachmentFormat[] { - const attachmentFormats: ProofAttachmentFormat[] = [] - - this.formats.forEach((format) => { - const attachment = this.proposalsAttach.find((attachment) => attachment.id === format.attachmentId) - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) - } - - attachmentFormats.push({ format, attachment }) - }) - return attachmentFormats - } - - @IsValidMessageType(V2ProposalPresentationMessage.type) - public readonly type = V2ProposalPresentationMessage.type.messageTypeUri - public static readonly type = parseMessageType(`https://didcomm.org/present-proof/2.0/propose-presentation`) - - @IsString() - @IsOptional() - public comment?: string - - @Expose({ name: 'goal_code' }) - @IsString() - @IsOptional() - public goalCode?: string - - @Expose({ name: 'will_confirm' }) - @IsBoolean() - public willConfirm = false - - @Expose({ name: 'formats' }) - @Type(() => ProofFormatSpec) - @IsArray() - @ValidateNested({ each: true }) - @IsInstance(ProofFormatSpec, { each: true }) - public formats!: ProofFormatSpec[] - - @Expose({ name: 'proposals~attach' }) - @Type(() => Attachment) - @IsArray() - @ValidateNested({ each: true }) - @IsInstance(Attachment, { each: true }) - public proposalsAttach!: Attachment[] -} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposePresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposePresentationMessage.ts new file mode 100644 index 0000000000..385925a5d0 --- /dev/null +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2ProposePresentationMessage.ts @@ -0,0 +1,62 @@ +import { Expose, Type } from 'class-transformer' +import { IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AgentMessage } from '../../../../../agent/AgentMessage' +import { Attachment } from '../../../../../decorators/attachment/Attachment' +import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { uuid } from '../../../../../utils/uuid' +import { ProofFormatSpec } from '../../../models/ProofFormatSpec' + +export interface V2ProposePresentationMessageOptions { + id?: string + comment?: string + goalCode?: string + proposalAttachments: Attachment[] + formats: ProofFormatSpec[] +} + +export class V2ProposePresentationMessage extends AgentMessage { + public constructor(options: V2ProposePresentationMessageOptions) { + super() + + if (options) { + this.formats = [] + this.proposalAttachments = [] + this.id = options.id ?? uuid() + this.comment = options.comment + this.goalCode = options.goalCode + this.formats = options.formats + this.proposalAttachments = options.proposalAttachments + } + } + + @IsValidMessageType(V2ProposePresentationMessage.type) + public readonly type = V2ProposePresentationMessage.type.messageTypeUri + public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/propose-presentation') + + @IsString() + @IsOptional() + public comment?: string + + @Expose({ name: 'goal_code' }) + @IsString() + @IsOptional() + public goalCode?: string + + @Type(() => ProofFormatSpec) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(ProofFormatSpec, { each: true }) + public formats!: ProofFormatSpec[] + + @Expose({ name: 'proposals~attach' }) + @Type(() => Attachment) + @IsArray() + @ValidateNested({ each: true }) + @IsInstance(Attachment, { each: true }) + public proposalAttachments!: Attachment[] + + public getProposalAttachmentById(id: string): Attachment | undefined { + return this.proposalAttachments.find((attachment) => attachment.id === id) + } +} diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts b/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts index 060badc050..0723d3d792 100644 --- a/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts +++ b/packages/core/src/modules/proofs/protocol/v2/messages/V2RequestPresentationMessage.ts @@ -1,11 +1,8 @@ -import type { ProofAttachmentFormat } from '../../../formats/models/ProofAttachmentFormat' - import { Expose, Type } from 'class-transformer' import { IsArray, IsBoolean, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' import { AgentMessage } from '../../../../../agent/AgentMessage' import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../../error' import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' import { uuid } from '../../../../../utils/uuid' import { ProofFormatSpec } from '../../../models/ProofFormatSpec' @@ -16,8 +13,8 @@ export interface V2RequestPresentationMessageOptions { goalCode?: string presentMultiple?: boolean willConfirm?: boolean - parentThreadId?: string - attachmentInfo: ProofAttachmentFormat[] + formats: ProofFormatSpec[] + requestAttachments: Attachment[] } export class V2RequestPresentationMessage extends AgentMessage { @@ -26,68 +23,17 @@ export class V2RequestPresentationMessage extends AgentMessage { if (options) { this.formats = [] - this.requestPresentationsAttach = [] + this.requestAttachments = [] this.id = options.id ?? uuid() this.comment = options.comment this.goalCode = options.goalCode this.willConfirm = options.willConfirm ?? true this.presentMultiple = options.presentMultiple ?? false - - if (options.parentThreadId) { - this.setThread({ - parentThreadId: options.parentThreadId, - }) - } - - for (const entry of options.attachmentInfo) { - this.addRequestPresentationsAttachment(entry) - } + this.requestAttachments = options.requestAttachments + this.formats = options.formats } } - public addRequestPresentationsAttachment(attachment: ProofAttachmentFormat) { - this.formats.push(attachment.format) - this.requestPresentationsAttach.push(attachment.attachment) - } - - public getAttachmentByFormatIdentifier(formatIdentifier: string) { - const format = this.formats.find((x) => x.format === formatIdentifier) - if (!format) { - throw new AriesFrameworkError( - `Expected to find a format entry of type: ${formatIdentifier}, but none could be found.` - ) - } - - const attachment = this.requestPresentationsAttach.find((x) => x.id === format.attachmentId) - - if (!attachment) { - throw new AriesFrameworkError( - `Expected to find an attachment entry with id: ${format.attachmentId}, but none could be found.` - ) - } - - return attachment - } - - /** - * Every attachment has a corresponding entry in the formats array. - * This method pairs those together in a {@link ProofAttachmentFormat} object. - */ - public getAttachmentFormats(): ProofAttachmentFormat[] { - const attachmentFormats: ProofAttachmentFormat[] = [] - - this.formats.forEach((format) => { - const attachment = this.requestPresentationsAttach.find((attachment) => attachment.id === format.attachmentId) - - if (!attachment) { - throw new AriesFrameworkError(`Could not find a matching attachment with attachmentId: ${format.attachmentId}`) - } - - attachmentFormats.push({ format, attachment }) - }) - return attachmentFormats - } - @IsValidMessageType(V2RequestPresentationMessage.type) public readonly type = V2RequestPresentationMessage.type.messageTypeUri public static readonly type = parseMessageType('https://didcomm.org/present-proof/2.0/request-presentation') @@ -121,5 +67,9 @@ export class V2RequestPresentationMessage extends AgentMessage { @IsArray() @ValidateNested({ each: true }) @IsInstance(Attachment, { each: true }) - public requestPresentationsAttach!: Attachment[] + public requestAttachments!: Attachment[] + + public getRequestAttachmentById(id: string): Attachment | undefined { + return this.requestAttachments.find((attachment) => attachment.id == id) + } } diff --git a/packages/core/src/modules/proofs/protocol/v2/messages/index.ts b/packages/core/src/modules/proofs/protocol/v2/messages/index.ts index 8b0c4a005d..515b0afb9c 100644 --- a/packages/core/src/modules/proofs/protocol/v2/messages/index.ts +++ b/packages/core/src/modules/proofs/protocol/v2/messages/index.ts @@ -1,5 +1,5 @@ export * from './V2PresentationAckMessage' export * from './V2PresentationMessage' export * from './V2PresentationProblemReportMessage' -export * from './V2ProposalPresentationMessage' +export * from './V2ProposePresentationMessage' export * from './V2RequestPresentationMessage' diff --git a/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts b/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts index f145703dff..e8d6784f12 100644 --- a/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts +++ b/packages/core/src/modules/proofs/repository/ProofExchangeRecord.ts @@ -82,6 +82,14 @@ export class ProofExchangeRecord extends BaseRecord ): Promise { @@ -367,6 +367,15 @@ export class W3cCredentialService { const result = await this.w3cCredentialRepository.findSingleByQuery(agentContext, query) return result?.credential } + public getProofTypeByVerificationMethodType(verificationMethodType: string): string { + const suite = this.signatureSuiteRegistry.getByVerificationMethodType(verificationMethodType) + + if (!suite) { + throw new AriesFrameworkError(`No suite found for verification method type ${verificationMethodType}}`) + } + + return suite.proofType + } private getSignatureSuitesForCredential(agentContext: AgentContext, credential: W3cVerifiableCredential) { const WalletKeyPair = createWalletKeyPairClass(agentContext.wallet) diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index c339fbfb4e..d6f3eb266f 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -297,10 +297,7 @@ describe('W3cCredentialService', () => { const result = await w3cCredentialService.verifyPresentation(agentContext, { presentation: vp, - proofType: 'Ed25519Signature2018', challenge: '7bf32d0b-39d4-41f3-96b6-45de52988e4c', - verificationMethod: - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', }) expect(result.verified).toBe(true) diff --git a/packages/core/src/modules/vc/__tests__/contexts/citizenship_v2.ts b/packages/core/src/modules/vc/__tests__/contexts/citizenship_v2.ts new file mode 100644 index 0000000000..667571bea2 --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/citizenship_v2.ts @@ -0,0 +1,45 @@ +export const CITIZENSHIP_V2 = { + '@context': { + '@version': 1.1, + '@protected': true, + name: 'http://schema.org/name', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + image: { '@id': 'http://schema.org/image', '@type': '@id' }, + PermanentResidentCard: { + '@id': 'https://w3id.org/citizenship#PermanentResidentCard', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + name: 'http://schema.org/name', + identifier: 'http://schema.org/identifier', + image: { '@id': 'http://schema.org/image', '@type': '@id' }, + }, + }, + PermanentResident: { + '@id': 'https://w3id.org/citizenship#PermanentResident', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + ctzn: 'https://w3id.org/citizenship#', + schema: 'http://schema.org/', + xsd: 'http://www.w3.org/2001/XMLSchema#', + birthCountry: 'ctzn:birthCountry', + birthDate: { '@id': 'schema:birthDate', '@type': 'xsd:dateTime' }, + commuterClassification: 'ctzn:commuterClassification', + familyName: 'schema:familyName', + gender: 'schema:gender', + givenName: 'schema:givenName', + lprCategory: 'ctzn:lprCategory', + lprNumber: 'ctzn:lprNumber', + residentSince: { '@id': 'ctzn:residentSince', '@type': 'xsd:dateTime' }, + }, + }, + Person: 'http://schema.org/Person', + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/index.ts b/packages/core/src/modules/vc/__tests__/contexts/index.ts index c66801c24a..0d5bfff11a 100644 --- a/packages/core/src/modules/vc/__tests__/contexts/index.ts +++ b/packages/core/src/modules/vc/__tests__/contexts/index.ts @@ -8,4 +8,6 @@ export * from './schema_org' export * from './security_v1' export * from './security_v2' export * from './security_v3_unstable' +export * from './submission' export * from './vaccination_v1' +export * from './vaccination_v2' diff --git a/packages/core/src/modules/vc/__tests__/contexts/submission.ts b/packages/core/src/modules/vc/__tests__/contexts/submission.ts new file mode 100644 index 0000000000..4df5ca9b4f --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/submission.ts @@ -0,0 +1,15 @@ +export const PRESENTATION_SUBMISSION = { + '@context': { + '@version': 1.1, + PresentationSubmission: { + '@id': 'https://identity.foundation/presentation-exchange/#presentation-submission', + '@context': { + '@version': 1.1, + presentation_submission: { + '@id': 'https://identity.foundation/presentation-exchange/#presentation-submission', + '@type': '@json', + }, + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/contexts/vaccination_v2.ts b/packages/core/src/modules/vc/__tests__/contexts/vaccination_v2.ts new file mode 100644 index 0000000000..483c87134b --- /dev/null +++ b/packages/core/src/modules/vc/__tests__/contexts/vaccination_v2.ts @@ -0,0 +1,88 @@ +export const VACCINATION_V2 = { + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + name: 'http://schema.org/name', + image: 'http://schema.org/image', + VaccinationCertificate: { + '@id': 'https://w3id.org/vaccination#VaccinationCertificate', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + description: 'http://schema.org/description', + identifier: 'http://schema.org/identifier', + name: 'http://schema.org/name', + image: 'http://schema.org/image', + }, + }, + VaccinationEvent: { + '@id': 'https://w3id.org/vaccination#VaccinationEvent', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + administeringCentre: 'https://w3id.org/vaccination#administeringCentre', + batchNumber: 'https://w3id.org/vaccination#batchNumber', + countryOfVaccination: 'https://w3id.org/vaccination#countryOfVaccination', + dateOfVaccination: { + '@id': 'https://w3id.org/vaccination#dateOfVaccination', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + healthProfessional: 'https://w3id.org/vaccination#healthProfessional', + nextVaccinationDate: { + '@id': 'https://w3id.org/vaccination#nextVaccinationDate', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + order: 'https://w3id.org/vaccination#order', + recipient: { + '@id': 'https://w3id.org/vaccination#recipient', + '@type': 'https://w3id.org/vaccination#VaccineRecipient', + }, + vaccine: { + '@id': 'https://w3id.org/vaccination#VaccineEventVaccine', + '@type': 'https://w3id.org/vaccination#Vaccine', + }, + }, + }, + VaccineRecipient: { + '@id': 'https://w3id.org/vaccination#VaccineRecipient', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + birthDate: { + '@id': 'http://schema.org/birthDate', + '@type': 'http://www.w3.org/2001/XMLSchema#dateTime', + }, + familyName: 'http://schema.org/familyName', + gender: 'http://schema.org/gender', + givenName: 'http://schema.org/givenName', + }, + }, + Vaccine: { + '@id': 'https://w3id.org/vaccination#Vaccine', + '@context': { + '@version': 1.1, + '@protected': true, + id: '@id', + type: '@type', + atcCode: 'https://w3id.org/vaccination#atc-code', + disease: 'https://w3id.org/vaccination#disease', + event: { + '@id': 'https://w3id.org/vaccination#VaccineRecipientVaccineEvent', + '@type': 'https://w3id.org/vaccination#VaccineEvent', + }, + marketingAuthorizationHolder: 'https://w3id.org/vaccination#marketingAuthorizationHolder', + medicinalProductName: 'https://w3id.org/vaccination#medicinalProductName', + }, + }, + }, +} diff --git a/packages/core/src/modules/vc/__tests__/documentLoader.ts b/packages/core/src/modules/vc/__tests__/documentLoader.ts index 4d7aa89f0d..adf72dba7f 100644 --- a/packages/core/src/modules/vc/__tests__/documentLoader.ts +++ b/packages/core/src/modules/vc/__tests__/documentLoader.ts @@ -4,9 +4,18 @@ import type { DocumentLoaderResult } from '../libraries/jsonld' import jsonld from '../libraries/jsonld' -import { BBS_V1, EXAMPLES_V1, ODRL, SCHEMA_ORG, VACCINATION_V1 } from './contexts' +import { + BBS_V1, + EXAMPLES_V1, + ODRL, + PRESENTATION_SUBMISSION, + SCHEMA_ORG, + VACCINATION_V1, + VACCINATION_V2, +} from './contexts' import { X25519_V1 } from './contexts/X25519_v1' import { CITIZENSHIP_V1 } from './contexts/citizenship_v1' +import { CITIZENSHIP_V2 } from './contexts/citizenship_v2' import { CREDENTIALS_V1 } from './contexts/credentials_v1' import { DID_V1 } from './contexts/did_v1' import { ED25519_V1 } from './contexts/ed25519_v1' @@ -95,9 +104,12 @@ export const DOCUMENTS = { 'https://www.w3.org/ns/did/v1': DID_V1, 'https://w3.org/ns/did/v1': DID_V1, 'https://w3id.org/citizenship/v1': CITIZENSHIP_V1, + 'https://w3id.org/citizenship/v2': CITIZENSHIP_V2, 'https://www.w3.org/ns/odrl.jsonld': ODRL, 'http://schema.org/': SCHEMA_ORG, 'https://w3id.org/vaccination/v1': VACCINATION_V1, + 'https://w3id.org/vaccination/v2': VACCINATION_V2, + 'https://identity.foundation/presentation-exchange/submission/v1': PRESENTATION_SUBMISSION, 'https://mattr.global/contexts/vc-extensions/v1': MATTR_VC_EXTENSION_V1, 'https://purl.imsglobal.org/spec/ob/v3p0/context.json': PURL_OB_V3P0, 'https://w3c-ccg.github.io/vc-status-rl-2020/contexts/vc-revocation-list-2020/v1.jsonld': VC_REVOCATION_LIST_2020, diff --git a/packages/core/src/modules/vc/__tests__/fixtures.ts b/packages/core/src/modules/vc/__tests__/fixtures.ts index 491a388f98..9e8a6caa16 100644 --- a/packages/core/src/modules/vc/__tests__/fixtures.ts +++ b/packages/core/src/modules/vc/__tests__/fixtures.ts @@ -13,6 +13,36 @@ export const Ed25519Signature2018Fixtures = { }, }, }, + TEST_LD_DOCUMENT_2: { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/security/suites/ed25519-2020/v1', + 'https://w3id.org/citizenship/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: '', + identifier: '83627465', + name: 'Permanent Resident Card', + description: 'Government of Example Permanent Resident Card.', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + }, + TEST_LD_DOCUMENT_SIGNED: { '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], type: ['VerifiableCredential', 'UniversityDegreeCredential'], diff --git a/packages/core/src/modules/vc/constants.ts b/packages/core/src/modules/vc/constants.ts index 6b298625df..b166244ebf 100644 --- a/packages/core/src/modules/vc/constants.ts +++ b/packages/core/src/modules/vc/constants.ts @@ -5,6 +5,7 @@ export const SECURITY_CONTEXT_URL = SECURITY_CONTEXT_V2_URL export const SECURITY_X25519_CONTEXT_URL = 'https://w3id.org/security/suites/x25519-2019/v1' export const DID_V1_CONTEXT_URL = 'https://www.w3.org/ns/did/v1' export const CREDENTIALS_CONTEXT_V1_URL = 'https://www.w3.org/2018/credentials/v1' +export const BANKACCOUNT_CONTEXT_V1_URL = 'https://www.w3.org/2018/bankaccount/v1' export const SECURITY_CONTEXT_BBS_URL = 'https://w3id.org/security/bbs/v1' export const CREDENTIALS_ISSUER_URL = 'https://www.w3.org/2018/credentials#issuer' export const SECURITY_PROOF_URL = 'https://w3id.org/security#proof' diff --git a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts index 042550fd8e..7851859949 100644 --- a/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts +++ b/packages/core/src/modules/vc/models/W3cCredentialServiceOptions.ts @@ -39,8 +39,6 @@ export interface SignPresentationOptions { export interface VerifyPresentationOptions { presentation: W3cVerifiablePresentation - proofType: string - verificationMethod: string purpose?: ProofPurpose challenge?: string } diff --git a/packages/core/src/utils/__tests__/indyProofRequest.test.ts b/packages/core/src/utils/__tests__/indyProofRequest.test.ts deleted file mode 100644 index ef15b80f40..0000000000 --- a/packages/core/src/utils/__tests__/indyProofRequest.test.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { checkProofRequestForDuplicates } from '../indyProofRequest' - -import { - AriesFrameworkError, - AttributeFilter, - PredicateType, - ProofAttributeInfo, - ProofPredicateInfo, - ProofRequest, -} from '@aries-framework/core' - -describe('Present Proof', () => { - const credDefId = '9vPXgSpQJPkJEALbLXueBp:3:CL:57753:tag1' - const nonce = 'testtesttest12345' - - test('attribute names match', () => { - const attributes = { - age1: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - age2: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const proofRequest = new ProofRequest({ - name: 'proof-request', - version: '1.0', - nonce, - requestedAttributes: attributes, - }) - - expect(() => checkProofRequestForDuplicates(proofRequest)).not.toThrow() - }) - - test('attribute names match with predicates name', () => { - const attributes = { - attrib: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - predicate: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const proofRequest = new ProofRequest({ - name: 'proof-request', - version: '1.0', - nonce, - requestedAttributes: attributes, - requestedPredicates: predicates, - }) - - expect(() => checkProofRequestForDuplicates(proofRequest)).toThrowError(AriesFrameworkError) - }) -}) diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index c0dfd135df..8875930e15 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -14,3 +14,4 @@ export * from './type' export * from './indyIdentifiers' export * from './deepEquality' export * from './objectEquality' +export * from './MessageValidator' diff --git a/packages/core/src/utils/indyProofRequest.ts b/packages/core/src/utils/indyProofRequest.ts index 853bf4f742..df52b72cdc 100644 --- a/packages/core/src/utils/indyProofRequest.ts +++ b/packages/core/src/utils/indyProofRequest.ts @@ -25,7 +25,7 @@ function assertNoDuplicates(predicates: string[], attributeNames: string[]) { } // TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. -export function checkProofRequestForDuplicates(proofRequest: ProofRequest) { +export function assertNoDuplicateGroupsNamesInProofRequest(proofRequest: ProofRequest) { const attributes = attributeNamesToArray(proofRequest) const predicates = predicateNamesToArray(proofRequest) assertNoDuplicates(predicates, attributes) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 597cc6bda7..e8fd1f8a29 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -17,7 +17,7 @@ import type { import type { AgentModulesInput, EmptyModuleMap } from '../src/agent/AgentModules' import type { TrustPingReceivedEvent, TrustPingResponseReceivedEvent } from '../src/modules/connections/TrustPingEvents' import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' -import type { ProofAttributeInfo, ProofPredicateInfo } from '../src/modules/proofs/formats/indy/models' +import type { ProofAttributeInfo, ProofPredicateInfoOptions } from '../src/modules/proofs/formats/indy/models' import type { AutoAcceptProof } from '../src/modules/proofs/models/ProofAutoAcceptType' import type { Awaited, WalletConfig } from '../src/types' import type { CredDef, Schema } from 'indy-sdk' @@ -53,7 +53,6 @@ import { DidExchangeState, HandshakeProtocol, InjectionSymbols, - LogLevel, ProofEventTypes, } from '../src' import { Key, KeyType } from '../src/crypto' @@ -68,11 +67,7 @@ import { OutOfBandInvitation } from '../src/modules/oob/messages' import { OutOfBandRecord } from '../src/modules/oob/repository' import { PredicateType } from '../src/modules/proofs/formats/indy/models' import { ProofState } from '../src/modules/proofs/models/ProofState' -import { - PresentationPreview, - PresentationPreviewAttribute, - PresentationPreviewPredicate, -} from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import { V1PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import { customDocumentLoader } from '../src/modules/vc/__tests__/documentLoader' import { KeyDerivationMethod } from '../src/types' import { LinkedAttachment } from '../src/utils/LinkedAttachment' @@ -117,7 +112,7 @@ export function getAgentOptions = + subject instanceof ReplaySubject ? subject.asObservable() : subject return firstValueFrom( observable.pipe( filter((e) => previousState === undefined || e.payload.previousState === previousState), @@ -605,71 +601,24 @@ export async function issueCredential({ } } -export async function issueConnectionLessCredential({ - issuerAgent, - holderAgent, - credentialTemplate, -}: { - issuerAgent: Agent - holderAgent: Agent - credentialTemplate: IndyOfferCredentialFormat -}) { - const issuerReplay = new ReplaySubject() - const holderReplay = new ReplaySubject() - - issuerAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(issuerReplay) - holderAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(holderReplay) - - // eslint-disable-next-line prefer-const - let { credentialRecord: issuerCredentialRecord, message } = await issuerAgent.credentials.createOffer({ - comment: 'V1 Out of Band offer', - protocolVersion: 'v1', - credentialFormats: { - indy: { - attributes: credentialTemplate.attributes, - credentialDefinitionId: credentialTemplate.credentialDefinitionId, - }, - }, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) - - const { message: offerMessage } = await issuerAgent.oob.createLegacyConnectionlessInvitation({ - recordId: issuerCredentialRecord.id, - domain: 'https://example.org', - message, - }) - - await holderAgent.receiveMessage(offerMessage.toJSON()) - - let holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - const acceptOfferOptions: AcceptCredentialOfferOptions = { - credentialRecordId: holderCredentialRecord.id, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - } - - await holderAgent.credentials.acceptOffer(acceptOfferOptions) - - holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.Done, - }) - - issuerCredentialRecord = await waitForCredentialRecordSubject(issuerReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.Done, - }) +/** + * Returns mock of function with correct type annotations according to original function `fn`. + * It can be used also for class methods. + * + * @param fn function you want to mock + * @returns mock function with type annotations + */ +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export function mockFunction any>(fn: T): jest.MockedFunction { + return fn as jest.MockedFunction +} - return { - issuerCredential: issuerCredentialRecord, - holderCredential: holderCredentialRecord, - } +/** + * Set a property using a getter value on a mocked oject. + */ +// eslint-disable-next-line @typescript-eslint/ban-types +export function mockProperty(object: T, property: K, value: T[K]) { + Object.defineProperty(object, property, { get: () => value }) } export async function presentProof({ @@ -683,7 +632,7 @@ export async function presentProof({ holderAgent: Agent presentationTemplate: { attributes?: Record - predicates?: Record + predicates?: Record } }) { const verifierReplay = new ReplaySubject() @@ -704,7 +653,6 @@ export async function presentProof({ requestedAttributes: attributes, requestedPredicates: predicates, version: '1.0', - nonce: '947121108704767252195123', }, }, protocolVersion: 'v2', @@ -712,11 +660,8 @@ export async function presentProof({ let holderRecord = await holderProofExchangeRecordPromise - const requestedCredentials = await holderAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await holderAgent.proofs.selectCredentialsForRequest({ proofRecordId: holderRecord.id, - config: { - filterByPresentationPreview: true, - }, }) const verifierProofExchangeRecordPromise = waitForProofExchangeRecordSubject(verifierReplay, { @@ -739,7 +684,7 @@ export async function presentProof({ state: ProofState.Done, }) - verifierRecord = await verifierAgent.proofs.acceptPresentation(verifierRecord.id) + verifierRecord = await verifierAgent.proofs.acceptPresentation({ proofRecordId: verifierRecord.id }) holderRecord = await holderProofExchangeRecordPromise return { @@ -748,26 +693,6 @@ export async function presentProof({ } } -/** - * Returns mock of function with correct type annotations according to original function `fn`. - * It can be used also for class methods. - * - * @param fn function you want to mock - * @returns mock function with type annotations - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function mockFunction any>(fn: T): jest.MockedFunction { - return fn as jest.MockedFunction -} - -/** - * Set a property using a getter value on a mocked oject. - */ -// eslint-disable-next-line @typescript-eslint/ban-types -export function mockProperty(object: T, property: K, value: T[K]) { - Object.defineProperty(object, property, { get: () => value }) -} - // Helper type to get the type of the agents (with the custom modules) for the credential tests export type CredentialTestsAgent = Awaited>['aliceAgent'] export async function setupCredentialTests( @@ -857,12 +782,12 @@ export async function setupProofsTest(faberName: string, aliceName: string, auto const unique = uuid().substring(0, 4) - const faberAgentOptions = getAgentOptions(`${faberName}-${unique}`, { + const faberAgentOptions = getAgentOptions(`${faberName} - ${unique}`, { autoAcceptProofs, endpoints: ['rxjs:faber'], }) - const aliceAgentOptions = getAgentOptions(`${aliceName}-${unique}`, { + const aliceAgentOptions = getAgentOptions(`${aliceName} - ${unique}`, { autoAcceptProofs, endpoints: ['rxjs:alice'], }) @@ -893,26 +818,26 @@ export async function setupProofsTest(faberName: string, aliceName: string, auto const faberConnection = agentAConnection const aliceConnection = agentBConnection - const presentationPreview = new PresentationPreview({ + const presentationPreview = new V1PresentationPreview({ attributes: [ - new PresentationPreviewAttribute({ + { name: 'name', credentialDefinitionId: definition.id, referent: '0', value: 'John', - }), - new PresentationPreviewAttribute({ + }, + { name: 'image_0', credentialDefinitionId: definition.id, - }), + }, ], predicates: [ - new PresentationPreviewPredicate({ + { name: 'age', credentialDefinitionId: definition.id, predicate: PredicateType.GreaterThanOrEqualTo, threshold: 50, - }), + }, ], }) diff --git a/packages/core/tests/logger.ts b/packages/core/tests/logger.ts index 46c7066acc..cff2e39586 100644 --- a/packages/core/tests/logger.ts +++ b/packages/core/tests/logger.ts @@ -14,7 +14,7 @@ function logToTransport(logObject: ILogObject) { } export class TestLogger extends BaseLogger { - private logger: Logger + public readonly logger: Logger // Map our log levels to tslog levels private tsLogLevelMap = { @@ -27,29 +27,40 @@ export class TestLogger extends BaseLogger { [LogLevel.fatal]: 'fatal', } as const - public constructor(logLevel: LogLevel, name?: string) { + public static fromLogger(logger: TestLogger, name?: string) { + return new TestLogger(logger.logLevel, name, logger.logger) + } + + public constructor(logLevel: LogLevel, name?: string, logger?: Logger) { super(logLevel) - this.logger = new Logger({ - name, - minLevel: this.logLevel == LogLevel.off ? undefined : this.tsLogLevelMap[this.logLevel], - ignoreStackLevels: 5, - attachedTransports: [ - { - transportLogger: { - silly: logToTransport, - debug: logToTransport, - trace: logToTransport, - info: logToTransport, - warn: logToTransport, - error: logToTransport, - fatal: logToTransport, + if (logger) { + this.logger = logger.getChildLogger({ + name, + minLevel: this.logLevel == LogLevel.off ? undefined : this.tsLogLevelMap[this.logLevel], + }) + } else { + this.logger = new Logger({ + name, + minLevel: this.logLevel == LogLevel.off ? undefined : this.tsLogLevelMap[this.logLevel], + ignoreStackLevels: 5, + attachedTransports: [ + { + transportLogger: { + silly: logToTransport, + debug: logToTransport, + trace: logToTransport, + info: logToTransport, + warn: logToTransport, + error: logToTransport, + fatal: logToTransport, + }, + // always log to file + minLevel: 'silly', }, - // always log to file - minLevel: 'silly', - }, - ], - }) + ], + }) + } } private log(level: Exclude, message: string, data?: Record): void { @@ -93,6 +104,6 @@ export class TestLogger extends BaseLogger { } } -const testLogger = new TestLogger(LogLevel.error) +const testLogger = new TestLogger(LogLevel.off) export default testLogger diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 4df883e972..94161fd838 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { CreateOfferOptions, DefaultCredentialProtocols } from '../src/modules/credentials' +import type { CreateCredentialOfferOptions, DefaultCredentialProtocols } from '../src/modules/credentials' import type { AgentMessage, AgentMessageReceivedEvent } from '@aries-framework/core' import { Subject } from 'rxjs' @@ -58,7 +58,7 @@ describe('out of band', () => { let faberAgent: Agent let aliceAgent: Agent - let credentialTemplate: CreateOfferOptions + let credentialTemplate: CreateCredentialOfferOptions beforeAll(async () => { const faberMessages = new Subject() diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index 271bd089c3..45fa30e713 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -1,5 +1,5 @@ import type { Agent, ConnectionRecord, ProofExchangeRecord } from '../src' -import type { PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' +import type { V1PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' import { @@ -21,7 +21,7 @@ describe('Present Proof Subprotocol', () => { let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: PresentationPreview + let presentationPreview: V1PresentationPreview beforeAll(async () => { testLogger.test('Initializing the agents') @@ -57,7 +57,6 @@ describe('Present Proof Subprotocol', () => { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, }, @@ -87,11 +86,8 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, @@ -107,7 +103,7 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') @@ -161,7 +157,6 @@ describe('Present Proof Subprotocol', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -181,11 +176,8 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, @@ -202,7 +194,7 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') @@ -232,7 +224,6 @@ describe('Present Proof Subprotocol', () => { indy: { name: 'abc', version: '1.0', - nonce: '947121108704767252195126', attributes: presentationPreview.attributes, predicates: presentationPreview.predicates, }, @@ -262,11 +253,8 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, @@ -282,7 +270,7 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation provided by Alice testLogger.test('Faber accepts the presentation provided by Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she received a presentation acknowledgement testLogger.test('Alice waits until she receives a presentation acknowledgement') @@ -336,7 +324,6 @@ describe('Present Proof Subprotocol', () => { indy: { name: 'proof-request', version: '1.0', - nonce: '1298236324864', requestedAttributes: attributes, requestedPredicates: predicates, }, @@ -356,11 +343,8 @@ describe('Present Proof Subprotocol', () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.autoSelectCredentialsForProofRequest({ + const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, - config: { - filterByPresentationPreview: true, - }, }) await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, @@ -377,7 +361,7 @@ describe('Present Proof Subprotocol', () => { // Faber accepts the presentation testLogger.test('Faber accept the presentation from Alice') - await faberAgent.proofs.acceptPresentation(faberProofExchangeRecord.id) + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits until she receives a presentation acknowledgement testLogger.test('Alice waits for acceptance by Faber') diff --git a/packages/openid4vc-client/tests/fixtures.ts b/packages/openid4vc-client/tests/fixtures.ts index 44936b9169..e739859415 100644 --- a/packages/openid4vc-client/tests/fixtures.ts +++ b/packages/openid4vc-client/tests/fixtures.ts @@ -64,7 +64,7 @@ export const getMetadataResponse = { }, } -export const aquireAccessTokenResponse = { +export const acquireAccessTokenResponse = { access_token: '7nikUotMQefxn7oRX56R7MDNE7KJTGfwGjOkHzGaUIG', expires_in: 3600, scope: 'OpenBadgeCredential', diff --git a/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts b/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts index fe2d5b1dbc..8b01c14c17 100644 --- a/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts +++ b/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts @@ -1,6 +1,6 @@ import type { KeyDidCreateOptions } from '@aries-framework/core' -import { Agent, KeyType, LogLevel, W3cCredentialRecord, W3cVcModule } from '@aries-framework/core' +import { Agent, KeyType, W3cCredentialRecord, W3cVcModule } from '@aries-framework/core' import nock, { cleanAll, enableNetConnect } from 'nock' import { didKeyToInstanceOfKey } from '../../core/src/modules/dids/helpers' @@ -9,9 +9,7 @@ import { getAgentOptions } from '../../core/tests/helpers' import { OpenId4VcClientModule } from '@aries-framework/openid4vc-client' -import { TestLogger } from '../../core/tests/logger' - -import { aquireAccessTokenResponse, credentialRequestResponse, getMetadataResponse } from './fixtures' +import { acquireAccessTokenResponse, credentialRequestResponse, getMetadataResponse } from './fixtures' describe('OpenId4VcClient', () => { let agent: Agent<{ @@ -22,9 +20,7 @@ describe('OpenId4VcClient', () => { beforeEach(async () => { const agentOptions = getAgentOptions( 'OpenId4VcClient Agent', - { - logger: new TestLogger(LogLevel.test), - }, + {}, { openId4VcClient: new OpenId4VcClientModule(), w3cVc: new W3cVcModule({ @@ -62,7 +58,7 @@ describe('OpenId4VcClient', () => { .reply(200, getMetadataResponse) // setup access token response - httpMock.post('/oidc/v1/auth/token').reply(200, aquireAccessTokenResponse) + httpMock.post('/oidc/v1/auth/token').reply(200, acquireAccessTokenResponse) // setup credential request response httpMock.post('/oidc/v1/auth/credential').reply(200, credentialRequestResponse) diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index 86fb6dfe83..0f4c07e6da 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -3,16 +3,7 @@ import type { Agent } from '@aries-framework/core' import { sleep } from '../packages/core/src/utils/sleep' import { issueCredential, makeConnection, prepareForIssuance, presentProof } from '../packages/core/tests/helpers' -import { - V1CredentialPreview, - AttributeFilter, - CredentialState, - MediationState, - PredicateType, - ProofAttributeInfo, - ProofPredicateInfo, - ProofState, -} from '@aries-framework/core' +import { V1CredentialPreview, CredentialState, MediationState, ProofState } from '@aries-framework/core' export async function e2eTest({ mediatorAgent, @@ -63,9 +54,9 @@ export async function e2eTest({ // Present Proof from recipient to sender const definitionRestriction = [ - new AttributeFilter({ + { credentialDefinitionId: definition.id, - }), + }, ] const { holderProof, verifierProof } = await presentProof({ verifierAgent: senderAgent, @@ -73,18 +64,18 @@ export async function e2eTest({ verifierConnectionId: senderRecipientConnection.id, presentationTemplate: { attributes: { - name: new ProofAttributeInfo({ + name: { name: 'name', restrictions: definitionRestriction, - }), + }, }, predicates: { - olderThan21: new ProofPredicateInfo({ + olderThan21: { name: 'age', restrictions: definitionRestriction, - predicateType: PredicateType.LessThan, + predicateType: '<=', predicateValue: 20000712, - }), + }, }, }, }) diff --git a/yarn.lock b/yarn.lock index 4a8447c5fe..63404b7c30 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6085,11 +6085,6 @@ humanize-ms@^1.2.1: dependencies: ms "^2.0.0" -husky@^7.0.1: - version "7.0.4" - resolved "https://registry.yarnpkg.com/husky/-/husky-7.0.4.tgz#242048245dc49c8fb1bf0cc7cfb98dd722531535" - integrity sha512-vbaCKN2QLtP/vD4yvs6iz6hBEo6wkSzs8HpRah1Z6aGmF2KW5PdYuAd7uX5a+OyBZHBhd+TFLqgjUgytQr4RvQ== - iconv-lite@0.4.24, iconv-lite@^0.4.24, iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" From 0c6d091676ba14bc32dd73d502589fb594fe7e96 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 9 Feb 2023 16:38:36 +0100 Subject: [PATCH 02/22] chore: fix type issues Signed-off-by: Timo Glastra --- .../LegacyIndyCredentialFormatService.ts | 110 ++++++++++-------- 1 file changed, 60 insertions(+), 50 deletions(-) diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts index e1fd945937..3ae72ed704 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -10,20 +10,20 @@ import type { AnonCredsCredentialMetadata } from '../utils/metadata' import type { CredentialFormatService, AgentContext, - FormatCreateProposalOptions, - FormatCreateProposalReturn, - FormatProcessOptions, - FormatAcceptProposalOptions, - FormatCreateOfferReturn, - FormatCreateOfferOptions, - FormatAcceptOfferOptions, + CredentialFormatCreateProposalOptions, + CredentialFormatCreateProposalReturn, + CredentialFormatProcessOptions, + CredentialFormatAcceptProposalOptions, + CredentialFormatCreateOfferReturn, + CredentialFormatCreateOfferOptions, + CredentialFormatAcceptOfferOptions, CredentialFormatCreateReturn, - FormatAcceptRequestOptions, - FormatProcessCredentialOptions, - FormatAutoRespondProposalOptions, - FormatAutoRespondOfferOptions, - FormatAutoRespondRequestOptions, - FormatAutoRespondCredentialOptions, + CredentialFormatAcceptRequestOptions, + CredentialFormatProcessCredentialOptions, + CredentialFormatAutoRespondProposalOptions, + CredentialFormatAutoRespondOfferOptions, + CredentialFormatAutoRespondRequestOptions, + CredentialFormatAutoRespondCredentialOptions, CredentialExchangeRecord, CredentialPreviewAttributeOptions, LinkedAttachment, @@ -80,8 +80,8 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic */ public async createProposal( agentContext: AgentContext, - { credentialFormats, credentialRecord }: FormatCreateProposalOptions - ): Promise { + { credentialFormats, credentialRecord }: CredentialFormatCreateProposalOptions + ): Promise { const format = new CredentialFormatSpec({ format: INDY_CRED_FILTER, }) @@ -106,7 +106,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic } const proposalJson = JsonTransformer.toJSON(proposal) - const attachment = this.getFormatData(proposalJson, format.attachId) + const attachment = this.getFormatData(proposalJson, format.attachmentId) const { previewAttributes } = this.getCredentialLinkedAttachments( indyFormat.attributes, @@ -122,7 +122,10 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic return { format, attachment, previewAttributes } } - public async processProposal(agentContext: AgentContext, { attachment }: FormatProcessOptions): Promise { + public async processProposal( + agentContext: AgentContext, + { attachment }: CredentialFormatProcessOptions + ): Promise { const proposalJson = attachment.getDataAsJson() // fromJSON also validates @@ -132,12 +135,12 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic public async acceptProposal( agentContext: AgentContext, { - attachId, + attachmentId, credentialFormats, credentialRecord, proposalAttachment, - }: FormatAcceptProposalOptions - ): Promise { + }: CredentialFormatAcceptProposalOptions + ): Promise { const indyFormat = credentialFormats?.indy const credentialProposal = JsonTransformer.fromJSON(proposalAttachment.getDataAsJson(), IndyCredPropose) @@ -158,7 +161,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { credentialRecord, - attachId, + attachmentId, attributes, credentialDefinitionId, linkedAttachments: indyFormat?.linkedAttachments, @@ -176,8 +179,12 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic */ public async createOffer( agentContext: AgentContext, - { credentialFormats, credentialRecord, attachId }: FormatCreateOfferOptions - ): Promise { + { + credentialFormats, + credentialRecord, + attachmentId, + }: CredentialFormatCreateOfferOptions + ): Promise { const indyFormat = credentialFormats.indy if (!indyFormat) { @@ -186,7 +193,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { credentialRecord, - attachId, + attachmentId, attributes: indyFormat.attributes, credentialDefinitionId: indyFormat.credentialDefinitionId, linkedAttachments: indyFormat.linkedAttachments, @@ -195,7 +202,10 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic return { format, attachment, previewAttributes } } - public async processOffer(agentContext: AgentContext, { attachment, credentialRecord }: FormatProcessOptions) { + public async processOffer( + agentContext: AgentContext, + { attachment, credentialRecord }: CredentialFormatProcessOptions + ) { agentContext.config.logger.debug(`Processing indy credential offer for credential record ${credentialRecord.id}`) const credOffer = attachment.getDataAsJson() @@ -209,7 +219,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic public async acceptOffer( agentContext: AgentContext, - { credentialRecord, attachId, offerAttachment }: FormatAcceptOfferOptions + { credentialRecord, attachmentId, offerAttachment }: CredentialFormatAcceptOfferOptions ): Promise { const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) @@ -244,11 +254,11 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic }) const format = new CredentialFormatSpec({ - attachId, + attachmentId, format: INDY_CRED_REQUEST, }) - const attachment = this.getFormatData(credentialRequest, format.attachId) + const attachment = this.getFormatData(credentialRequest, format.attachmentId) return { format, attachment } } @@ -263,7 +273,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic * We don't have any models to validate an indy request object, for now this method does nothing */ // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async processRequest(agentContext: AgentContext, options: FormatProcessOptions): Promise { + public async processRequest(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise { // not needed for Indy } @@ -271,10 +281,10 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic agentContext: AgentContext, { credentialRecord, - attachId, + attachmentId, offerAttachment, requestAttachment, - }: FormatAcceptRequestOptions + }: CredentialFormatAcceptRequestOptions ): Promise { // Assert credential attributes const credentialAttributes = credentialRecord.credentialAttributes @@ -308,11 +318,11 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic } const format = new CredentialFormatSpec({ - attachId, + attachmentId, format: INDY_CRED, }) - const attachment = this.getFormatData(credential, format.attachId) + const attachment = this.getFormatData(credential, format.attachmentId) return { format, attachment } } @@ -323,7 +333,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic */ public async processCredential( agentContext: AgentContext, - { credentialRecord, attachment }: FormatProcessCredentialOptions + { credentialRecord, attachment }: CredentialFormatProcessCredentialOptions ): Promise { const credentialRequestMetadata = credentialRecord.metadata.get( AnonCredsCredentialRequestMetadataKey @@ -412,15 +422,15 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic } /** - * Gets the attachment object for a given attachId. We need to get out the correct attachId for + * Gets the attachment object for a given attachmentId. We need to get out the correct attachmentId for * indy and then find the corresponding attachment (if there is one) - * @param formats the formats object containing the attachId + * @param formats the formats object containing the attachmentId * @param messageAttachments the attachments containing the payload * @returns The Attachment if found or undefined * */ public getAttachment(formats: CredentialFormatSpec[], messageAttachments: Attachment[]): Attachment | undefined { - const supportedAttachmentIds = formats.filter((f) => this.supportsFormat(f.format)).map((f) => f.attachId) + const supportedAttachmentIds = formats.filter((f) => this.supportsFormat(f.format)).map((f) => f.attachmentId) const supportedAttachment = messageAttachments.find((attachment) => supportedAttachmentIds.includes(attachment.id)) return supportedAttachment @@ -433,9 +443,9 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic await anonCredsHolderService.deleteCredential(agentContext, credentialRecordId) } - public shouldAutoRespondToProposal( + public async shouldAutoRespondToProposal( agentContext: AgentContext, - { offerAttachment, proposalAttachment }: FormatAutoRespondProposalOptions + { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondProposalOptions ) { const credentialProposalJson = proposalAttachment.getDataAsJson() const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) @@ -448,9 +458,9 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic return credentialProposal.credentialDefinitionId === credentialOfferJson.cred_def_id } - public shouldAutoRespondToOffer( + public async shouldAutoRespondToOffer( agentContext: AgentContext, - { offerAttachment, proposalAttachment }: FormatAutoRespondOfferOptions + { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondOfferOptions ) { const credentialProposalJson = proposalAttachment.getDataAsJson() const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) @@ -463,9 +473,9 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic return credentialProposal.credentialDefinitionId === credentialOfferJson.cred_def_id } - public shouldAutoRespondToRequest( + public async shouldAutoRespondToRequest( agentContext: AgentContext, - { offerAttachment, requestAttachment }: FormatAutoRespondRequestOptions + { offerAttachment, requestAttachment }: CredentialFormatAutoRespondRequestOptions ) { const credentialOfferJson = offerAttachment.getDataAsJson() const credentialRequestJson = requestAttachment.getDataAsJson() @@ -473,9 +483,9 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic return credentialOfferJson.cred_def_id == credentialRequestJson.cred_def_id } - public shouldAutoRespondToCredential( + public async shouldAutoRespondToCredential( agentContext: AgentContext, - { credentialRecord, requestAttachment, credentialAttachment }: FormatAutoRespondCredentialOptions + { credentialRecord, requestAttachment, credentialAttachment }: CredentialFormatAutoRespondCredentialOptions ) { const credentialJson = credentialAttachment.getDataAsJson() const credentialRequestJson = requestAttachment.getDataAsJson() @@ -495,24 +505,24 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic agentContext: AgentContext, { credentialRecord, - attachId, + attachmentId, credentialDefinitionId, attributes, linkedAttachments, }: { credentialDefinitionId: string credentialRecord: CredentialExchangeRecord - attachId?: string + attachmentId?: string attributes: CredentialPreviewAttributeOptions[] linkedAttachments?: LinkedAttachment[] } - ): Promise { + ): Promise { const anonCredsIssuerService = agentContext.dependencyManager.resolve(AnonCredsIssuerServiceSymbol) // if the proposal has an attachment Id use that, otherwise the generated id of the formats object const format = new CredentialFormatSpec({ - attachId: attachId, + attachmentId: attachmentId, format: INDY_CRED_ABSTRACT, }) @@ -532,7 +542,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic credentialDefinitionId: offer.cred_def_id, }) - const attachment = this.getFormatData(offer, format.attachId) + const attachment = this.getFormatData(offer, format.attachmentId) return { format, attachment, previewAttributes } } From e941d5a5d9ac3ab3a6afc92d219e3605cd3c9af0 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 9 Feb 2023 22:21:18 +0100 Subject: [PATCH 03/22] feat: initial version legacy indy proof format Signed-off-by: Timo Glastra --- .../src/formats/AnonCredsProofFormat.ts | 89 +++ .../src/formats/LegacyIndyProofFormat.ts | 38 ++ .../formats/LegacyIndyProofFormatService.ts | 597 ++++++++++++++++++ .../LegacyIndyCredentialFormatService.test.ts | 2 + .../LegacyIndyProofFormatService.test.ts | 296 +++++++++ .../util/__tests__/areRequestsEqual.test.ts | 419 ++++++++++++ .../__tests__/hasDuplicateGroupNames.test.ts | 70 ++ .../sortRequestedCredentialsMatches.test.ts | 57 ++ .../src/formats/util/areRequestsEqual.ts | 156 +++++ .../formats/util/createRequestFromPreview.ts | 89 +++ .../formats/util/hasDuplicateGroupNames.ts | 23 + packages/anoncreds/src/formats/util/index.ts | 4 + .../util/sortRequestedCredentialsMatches.ts | 33 + packages/anoncreds/src/models/exchange.ts | 41 +- packages/anoncreds/src/models/internal.ts | 10 +- .../services/AnonCredsHolderServiceOptions.ts | 4 +- .../src/services/AnonCredsVerifierService.ts | 3 +- .../core/src/modules/proofs/models/index.ts | 1 + .../services/IndySdkHolderService.ts | 28 +- .../services/IndySdkRevocationService.ts | 16 +- .../services/IndySdkVerifierService.ts | 3 +- 21 files changed, 1928 insertions(+), 51 deletions(-) create mode 100644 packages/anoncreds/src/formats/AnonCredsProofFormat.ts create mode 100644 packages/anoncreds/src/formats/LegacyIndyProofFormat.ts create mode 100644 packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts create mode 100644 packages/anoncreds/src/formats/__tests__/LegacyIndyProofFormatService.test.ts create mode 100644 packages/anoncreds/src/formats/util/__tests__/areRequestsEqual.test.ts create mode 100644 packages/anoncreds/src/formats/util/__tests__/hasDuplicateGroupNames.test.ts create mode 100644 packages/anoncreds/src/formats/util/__tests__/sortRequestedCredentialsMatches.test.ts create mode 100644 packages/anoncreds/src/formats/util/areRequestsEqual.ts create mode 100644 packages/anoncreds/src/formats/util/createRequestFromPreview.ts create mode 100644 packages/anoncreds/src/formats/util/hasDuplicateGroupNames.ts create mode 100644 packages/anoncreds/src/formats/util/index.ts create mode 100644 packages/anoncreds/src/formats/util/sortRequestedCredentialsMatches.ts diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormat.ts b/packages/anoncreds/src/formats/AnonCredsProofFormat.ts new file mode 100644 index 0000000000..2bfeb689dc --- /dev/null +++ b/packages/anoncreds/src/formats/AnonCredsProofFormat.ts @@ -0,0 +1,89 @@ +import type { + AnonCredsNonRevokedInterval, + AnonCredsPredicateType, + AnonCredsProof, + AnonCredsProofRequest, + AnonCredsRequestedAttribute, + AnonCredsRequestedAttributeMatch, + AnonCredsRequestedPredicate, + AnonCredsRequestedPredicateMatch, + AnonCredsSelectedCredentials, +} from '../models' +import type { ProofFormat } from '@aries-framework/core' + +export interface AnonCredsPresentationPreviewAttribute { + name: string + credentialDefinitionId?: string + mimeType?: string + value?: string + referent?: string +} + +export interface AnonCredsPresentationPreviewPredicate { + name: string + credentialDefinitionId: string + predicate: AnonCredsPredicateType + threshold: number +} + +/** + * Interface for creating an anoncreds proof proposal. + */ +export interface AnonCredsProposeProofFormat { + name?: string + version?: string + attributes?: AnonCredsPresentationPreviewAttribute[] + predicates?: AnonCredsPresentationPreviewPredicate[] +} + +/** + * Interface for creating an anoncreds proof request. + */ +export interface AnonCredsRequestProofFormat { + name: string + version: string + nonRevoked?: AnonCredsNonRevokedInterval + requestedAttributes?: Record + requestedPredicates?: Record +} + +/** + * Interface for getting credentials for an indy proof request. + */ +export interface AnonCredsCredentialsForProofRequest { + attributes: Record + predicates: Record +} + +export interface AnonCredsGetCredentialsForProofRequestOptions { + filterByNonRevocationRequirements?: boolean +} + +export interface AnonCredsProofFormat extends ProofFormat { + formatKey: 'anoncreds' + + proofFormats: { + createProposal: AnonCredsProposeProofFormat + acceptProposal: { + name?: string + version?: string + } + createRequest: AnonCredsRequestProofFormat + acceptRequest: AnonCredsSelectedCredentials + + getCredentialsForRequest: { + input: AnonCredsGetCredentialsForProofRequestOptions + output: AnonCredsCredentialsForProofRequest + } + selectCredentialsForRequest: { + input: AnonCredsGetCredentialsForProofRequestOptions + output: AnonCredsSelectedCredentials + } + } + + formatData: { + proposal: AnonCredsProofRequest + request: AnonCredsProofRequest + presentation: AnonCredsProof + } +} diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts new file mode 100644 index 0000000000..c2dfc2cf0d --- /dev/null +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts @@ -0,0 +1,38 @@ +import type { + AnonCredsProposeProofFormat, + AnonCredsRequestProofFormat, + AnonCredsGetCredentialsForProofRequestOptions, + AnonCredsCredentialsForProofRequest, +} from './AnonCredsProofFormat' +import type { AnonCredsProof, AnonCredsProofRequest, AnonCredsSelectedCredentials } from '../models' +import type { ProofFormat } from '@aries-framework/core' + +export interface LegacyIndyProofFormat extends ProofFormat { + formatKey: 'indy' + + proofFormats: { + createProposal: AnonCredsProposeProofFormat + acceptProposal: { + name?: string + version?: string + } + createRequest: AnonCredsRequestProofFormat + acceptRequest: AnonCredsSelectedCredentials + + getCredentialsForRequest: { + input: AnonCredsGetCredentialsForProofRequestOptions + output: AnonCredsCredentialsForProofRequest + } + selectCredentialsForRequest: { + input: AnonCredsGetCredentialsForProofRequestOptions + output: AnonCredsSelectedCredentials + } + } + + formatData: { + // TODO: Custom restrictions to remove `_id` from restrictions? + proposal: AnonCredsProofRequest + request: AnonCredsProofRequest + presentation: AnonCredsProof + } +} diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts new file mode 100644 index 0000000000..86186e2c3c --- /dev/null +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -0,0 +1,597 @@ +import type { + AnonCredsCredentialsForProofRequest, + AnonCredsGetCredentialsForProofRequestOptions, +} from './AnonCredsProofFormat' +import type { LegacyIndyProofFormat } from './LegacyIndyProofFormat' +import type { + AnonCredsCredentialDefinition, + AnonCredsProof, + AnonCredsProofRequest, + AnonCredsRequestedAttribute, + AnonCredsRequestedAttributeMatch, + AnonCredsRequestedPredicate, + AnonCredsRequestedPredicateMatch, + AnonCredsSchema, + AnonCredsSelectedCredentials, +} from '../models' +import type { AnonCredsHolderService, AnonCredsVerifierService, GetCredentialsForProofRequestReturn } from '../services' +import type { + ProofFormatService, + AgentContext, + ProofFormatCreateReturn, + FormatCreateRequestOptions, + ProofFormatCreateProposalOptions, + ProofFormatProcessOptions, + ProofFormatAcceptProposalOptions, + ProofFormatAcceptRequestOptions, + ProofFormatProcessPresentationOptions, + ProofFormatGetCredentialsForRequestOptions, + ProofFormatGetCredentialsForRequestReturn, + ProofFormatSelectCredentialsForRequestOptions, + ProofFormatSelectCredentialsForRequestReturn, + ProofFormatAutoRespondProposalOptions, + ProofFormatAutoRespondRequestOptions, + IndyGetCredentialsForProofRequestOptions, +} from '@aries-framework/core' + +import { AriesFrameworkError, Attachment, AttachmentData, JsonEncoder, ProofFormatSpec } from '@aries-framework/core' + +import { AnonCredsVerifierServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' +import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' +// TODO: unify utils +import { checkValidEncoding, encode } from '../utils/credential' + +import { + sortRequestedCredentialsMatches, + createRequestFromPreview, + hasDuplicateGroupsNamesInProofRequest, + areAnonCredsProofRequestsEqual, +} from './util' + +const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' +const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' +const V2_INDY_PRESENTATION = 'hlindy/proof@v2.0' + +export class LegacyIndyProofFormatService implements ProofFormatService { + public readonly formatKey = 'indy' as const + + public async createProposal( + agentContext: AgentContext, + { attachmentId, proofFormats }: ProofFormatCreateProposalOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION_PROPOSAL, + attachmentId, + }) + + const indyFormat = proofFormats.indy + if (!indyFormat) { + throw Error('Missing indy format to create proposal attachment format') + } + + const proofRequest = createRequestFromPreview({ + attributes: indyFormat.attributes ?? [], + predicates: indyFormat.predicates ?? [], + name: indyFormat.name ?? 'Proof request', + version: indyFormat.version ?? '1.0', + nonce: await agentContext.wallet.generateNonce(), + }) + const attachment = this.getFormatData(proofRequest, format.attachmentId) + + return { attachment, format } + } + + public async processProposal(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { + // TODO: validation + const proposalJson = attachment.getDataAsJson() + + // Assert attribute and predicate (group) names do not match + if (hasDuplicateGroupsNamesInProofRequest(proposalJson)) { + throw new AriesFrameworkError('Attribute and predicate (group) names must be unique in proof request') + } + } + + public async acceptProposal( + agentContext: AgentContext, + { proposalAttachment, attachmentId }: ProofFormatAcceptProposalOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION_REQUEST, + attachmentId, + }) + + const proposalJson = proposalAttachment.getDataAsJson() + + const request = { + ...proposalJson, + // We never want to reuse the nonce from the proposal, as this will allow replay attacks + nonce: await agentContext.wallet.generateNonce(), + } + + const attachment = this.getFormatData(request, format.attachmentId) + + return { attachment, format } + } + + public async createRequest( + agentContext: AgentContext, + { attachmentId, proofFormats }: FormatCreateRequestOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION_REQUEST, + attachmentId, + }) + + const indyFormat = proofFormats.indy + if (!indyFormat) { + throw Error('Missing indy format in create request attachment format') + } + + const request = { + name: indyFormat.name, + version: indyFormat.version, + nonce: await agentContext.wallet.generateNonce(), + requested_attributes: indyFormat.requestedAttributes ?? {}, + requested_predicates: indyFormat.requestedPredicates ?? {}, + non_revoked: indyFormat.nonRevoked, + } satisfies AnonCredsProofRequest + + // Validate to make sure user provided correct input + if (hasDuplicateGroupsNamesInProofRequest(request)) { + throw new AriesFrameworkError('Attribute and predicate (group) names must be unique in proof request') + } + + const attachment = this.getFormatData(request, format.attachmentId) + + return { attachment, format } + } + + public async processRequest(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { + // TODO: validation + const requestJson = attachment.getDataAsJson() + + // Assert attribute and predicate (group) names do not match + if (hasDuplicateGroupsNamesInProofRequest(requestJson)) { + throw new AriesFrameworkError('Attribute and predicate (group) names must be unique in proof request') + } + } + + public async acceptRequest( + agentContext: AgentContext, + { proofFormats, requestAttachment, attachmentId }: ProofFormatAcceptRequestOptions + ): Promise { + const format = new ProofFormatSpec({ + format: V2_INDY_PRESENTATION, + attachmentId, + }) + const requestJson = requestAttachment.getDataAsJson() + + const indyFormat = proofFormats?.indy + + const selectedCredentials = + indyFormat ?? + (await this._selectCredentialsForRequest(agentContext, requestJson, { + filterByNonRevocationRequirements: true, + })) + + const proof = await this.createProof(agentContext, requestJson, selectedCredentials) + const attachment = this.getFormatData(proof, format.attachmentId) + + return { + attachment, + format, + } + } + + public async processPresentation( + agentContext: AgentContext, + { requestAttachment, attachment }: ProofFormatProcessPresentationOptions + ): Promise { + const verifierService = + agentContext.dependencyManager.resolve(AnonCredsVerifierServiceSymbol) + + const proofRequestJson = requestAttachment.getDataAsJson() + + // TODO: validation + const proofJson = attachment.getDataAsJson() + + for (const [referent, attribute] of Object.entries(proofJson.requested_proof.revealed_attrs)) { + if (!checkValidEncoding(attribute.raw, attribute.encoded)) { + throw new AriesFrameworkError( + `The encoded value for '${referent}' is invalid. ` + + `Expected '${encode(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + + for (const [, attributeGroup] of Object.entries(proofJson.requested_proof.revealed_attr_groups ?? {})) { + for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { + if (!checkValidEncoding(attribute.raw, attribute.encoded)) { + throw new AriesFrameworkError( + `The encoded value for '${attributeName}' is invalid. ` + + `Expected '${encode(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + } + + // TODO: pre verify proof json + // I'm not 100% sure how much indy does. Also if it checks whether the proof requests matches the proof + // @see https://github.com/hyperledger/aries-cloudagent-python/blob/master/aries_cloudagent/indy/sdk/verifier.py#L79-L164 + + const schemas = await this.getSchemas(agentContext, new Set(proofJson.identifiers.map((i) => i.schema_id))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(proofJson.identifiers.map((i) => i.cred_def_id)) + ) + + return await verifierService.verifyProof(agentContext, { + proofRequest: proofRequestJson, + proof: proofJson, + schemas, + credentialDefinitions, + // TODO: revocation registry definitions + revocationStates: {}, + }) + } + + public async getCredentialsForRequest( + agentContext: AgentContext, + { requestAttachment, proofFormats }: ProofFormatGetCredentialsForRequestOptions + ): Promise> { + const proofRequestJson = requestAttachment.getDataAsJson() + + // Set default values + const { filterByNonRevocationRequirements = true } = proofFormats?.indy ?? {} + + const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequestJson, { + filterByNonRevocationRequirements, + }) + + return credentialsForRequest + } + + public async selectCredentialsForRequest( + agentContext: AgentContext, + { requestAttachment, proofFormats }: ProofFormatSelectCredentialsForRequestOptions + ): Promise> { + const proofRequestJson = requestAttachment.getDataAsJson() + + // Set default values + const { filterByNonRevocationRequirements = true } = proofFormats?.indy ?? {} + + const selectedCredentials = this._selectCredentialsForRequest(agentContext, proofRequestJson, { + filterByNonRevocationRequirements, + }) + + return selectedCredentials + } + + public async shouldAutoRespondToProposal( + agentContext: AgentContext, + { proposalAttachment, requestAttachment }: ProofFormatAutoRespondProposalOptions + ): Promise { + const proposalJson = proposalAttachment.getDataAsJson() + const requestJson = requestAttachment.getDataAsJson() + + const areRequestsEqual = areAnonCredsProofRequestsEqual(proposalJson, requestJson) + agentContext.config.logger.debug(`AnonCreds request and proposal are are equal: ${areRequestsEqual}`, { + proposalJson, + requestJson, + }) + + return areRequestsEqual + } + + public async shouldAutoRespondToRequest( + agentContext: AgentContext, + { proposalAttachment, requestAttachment }: ProofFormatAutoRespondRequestOptions + ): Promise { + const proposalJson = proposalAttachment.getDataAsJson() + const requestJson = requestAttachment.getDataAsJson() + + return areAnonCredsProofRequestsEqual(proposalJson, requestJson) + } + + public async shouldAutoRespondToPresentation(): Promise { + // The presentation is already verified in processPresentation, so we can just return true here. + // It's only an ack, so it's just that we received the presentation. + return true + } + + public supportsFormat(formatIdentifier: string): boolean { + const supportedFormats = [V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION] + return supportedFormats.includes(formatIdentifier) + } + + private async _getCredentialsForRequest( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + options: IndyGetCredentialsForProofRequestOptions + ): Promise { + const credentialsForProofRequest: AnonCredsCredentialsForProofRequest = { + attributes: {}, + predicates: {}, + } + + for (const [referent, requestedAttribute] of Object.entries(proofRequest.requested_attributes)) { + const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequest, referent) + + credentialsForProofRequest.attributes[referent] = sortRequestedCredentialsMatches( + await Promise.all( + credentials.map(async (credential) => { + const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { + proofRequest, + requestedItem: requestedAttribute, + credential, + }) + + return { + credentialId: credential.credentialInfo.credentialId, + revealed: true, + credentialInfo: credential.credentialInfo, + timestamp: deltaTimestamp, + revoked, + } satisfies AnonCredsRequestedAttributeMatch + }) + ) + ) + + // We only attach revoked state if non-revocation is requested. So if revoked is true it means + // the credential is not applicable to the proof request + if (options.filterByNonRevocationRequirements) { + credentialsForProofRequest.attributes[referent] = credentialsForProofRequest.attributes[referent].filter( + (r) => !r.revoked + ) + } + } + + for (const [referent, requestedPredicate] of Object.entries(proofRequest.requested_predicates)) { + const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequest, referent) + + credentialsForProofRequest.predicates[referent] = sortRequestedCredentialsMatches( + await Promise.all( + credentials.map(async (credential) => { + const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { + proofRequest, + requestedItem: requestedPredicate, + credential, + }) + + return { + credentialId: credential.credentialInfo.credentialId, + credentialInfo: credential.credentialInfo, + timestamp: deltaTimestamp, + revoked, + } satisfies AnonCredsRequestedPredicateMatch + }) + ) + ) + + // We only attach revoked state if non-revocation is requested. So if revoked is true it means + // the credential is not applicable to the proof request + if (options.filterByNonRevocationRequirements) { + credentialsForProofRequest.predicates[referent] = credentialsForProofRequest.predicates[referent].filter( + (r) => !r.revoked + ) + } + } + + return credentialsForProofRequest + } + + private async _selectCredentialsForRequest( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + options: AnonCredsGetCredentialsForProofRequestOptions + ): Promise { + const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequest, options) + + const selectedCredentials: AnonCredsSelectedCredentials = { + attributes: {}, + predicates: {}, + selfAttestedAttributes: {}, + } + + Object.keys(credentialsForRequest.attributes).forEach((attributeName) => { + const attributeArray = credentialsForRequest.attributes[attributeName] + + if (attributeArray.length === 0) { + throw new AriesFrameworkError('Unable to automatically select requested attributes.') + } + + selectedCredentials.attributes[attributeName] = attributeArray[0] + }) + + Object.keys(credentialsForRequest.predicates).forEach((attributeName) => { + if (credentialsForRequest.predicates[attributeName].length === 0) { + throw new AriesFrameworkError('Unable to automatically select requested predicates.') + } else { + selectedCredentials.predicates[attributeName] = credentialsForRequest.predicates[attributeName][0] + } + }) + + return selectedCredentials + } + + private async getCredentialsForProofRequestReferent( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + attributeReferent: string + ): Promise { + const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + const credentials = await holderService.getCredentialsForProofRequest(agentContext, { + proofRequest, + attributeReferent, + }) + + return credentials + } + + /** + * Build schemas object needed to create and verify proof objects. + * + * Creates object with `{ schemaId: AnonCredsSchema }` mapping + * + * @param schemaIds List of schema ids + * @returns Object containing schemas for specified schema ids + * + */ + private async getSchemas(agentContext: AgentContext, schemaIds: Set) { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + + const schemas: { [key: string]: AnonCredsSchema } = {} + + for (const schemaId of schemaIds) { + const schemaRegistry = registryService.getRegistryForIdentifier(agentContext, schemaId) + const schemaResult = await schemaRegistry.getSchema(agentContext, schemaId) + + if (!schemaResult.schema) { + throw new AriesFrameworkError(`Schema not found for id ${schemaId}: ${schemaResult.resolutionMetadata.message}`) + } + + schemas[schemaId] = schemaResult.schema + } + + return schemas + } + + /** + * Build credential definitions object needed to create and verify proof objects. + * + * Creates object with `{ credentialDefinitionId: AnonCredsCredentialDefinition }` mapping + * + * @param credentialDefinitionIds List of credential definition ids + * @returns Object containing credential definitions for specified credential definition ids + * + */ + private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { + const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) + + const credentialDefinitions: { [key: string]: AnonCredsCredentialDefinition } = {} + + for (const credentialDefinitionId of credentialDefinitionIds) { + const credentialDefinitionRegistry = registryService.getRegistryForIdentifier( + agentContext, + credentialDefinitionId + ) + + const credentialDefinitionResult = await credentialDefinitionRegistry.getCredentialDefinition( + agentContext, + credentialDefinitionId + ) + + if (!credentialDefinitionResult.credentialDefinition) { + throw new AriesFrameworkError( + `Credential definition not found for id ${credentialDefinitionId}: ${credentialDefinitionResult.resolutionMetadata.message}` + ) + } + + credentialDefinitions[credentialDefinitionId] = credentialDefinitionResult.credentialDefinition + } + + return credentialDefinitions + } + + /** + * Create indy proof from a given proof request and requested credential object. + * + * @param proofRequest The proof request to create the proof for + * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof + * @returns indy proof object + */ + private async createProof( + agentContext: AgentContext, + proofRequest: AnonCredsProofRequest, + selectedCredentials: AnonCredsSelectedCredentials + ): Promise { + const holderService = agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) + + const credentialObjects = await Promise.all( + [...Object.values(selectedCredentials.attributes), ...Object.values(selectedCredentials.predicates)].map( + async (c) => c.credentialInfo ?? holderService.getCredential(agentContext, { credentialId: c.credentialId }) + ) + ) + + const schemas = await this.getSchemas(agentContext, new Set(credentialObjects.map((c) => c.schemaId))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(credentialObjects.map((c) => c.credentialDefinitionId)) + ) + + return await holderService.createProof(agentContext, { + proofRequest, + selectedCredentials, + schemas, + credentialDefinitions, + // TODO: resolve and pass revocation registries + revocationRegistries: {}, + }) + } + + private async getRevocationStatusForRequestedItem( + agentContext: AgentContext, + { + proofRequest, + requestedItem, + credential, + }: { + proofRequest: AnonCredsProofRequest + requestedItem: AnonCredsRequestedAttribute | AnonCredsRequestedPredicate + credential: GetCredentialsForProofRequestReturn[number] + } + ) { + // const indyRevocationService = agentContext.dependencyManager.resolve(IndyRevocationService) + + const requestNonRevoked = requestedItem.non_revoked ?? proofRequest.non_revoked + const credentialRevocationId = credential.credentialInfo.credentialRevocationId + const revocationRegistryId = credential.credentialInfo.revocationRegistryId + + // If revocation interval is present and the credential is revocable then fetch the revocation status of credentials for display + if (requestNonRevoked && credentialRevocationId && revocationRegistryId) { + agentContext.config.logger.trace( + `Presentation is requesting proof of non revocation, getting revocation status for credential`, + { + requestNonRevoked, + credentialRevocationId, + revocationRegistryId, + } + ) + + // TODO: update this to use the anoncreds registry + // Note presentation from-to's vs ledger from-to's: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals + // const status = await indyRevocationService.getRevocationStatus( + // agentContext, + // credentialRevocationId, + // revocationRegistryId, + // requestNonRevoked + // ) + + return { status: undefined, deltaTimestamp: undefined } + } + + return { revoked: undefined, deltaTimestamp: undefined } + } + + /** + * Returns an object of type {@link Attachment} for use in credential exchange messages. + * It looks up the correct format identifier and encodes the data as a base64 attachment. + * + * @param data The data to include in the attach object + * @param id the attach id from the formats component of the message + */ + private getFormatData(data: unknown, id: string): Attachment { + const attachment = new Attachment({ + id, + mimeType: 'application/json', + data: new AttachmentData({ + base64: JsonEncoder.toBase64(data), + }), + }) + + return attachment + } +} diff --git a/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts b/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts index 2449c81124..6641ea2de8 100644 --- a/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts +++ b/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts @@ -26,6 +26,8 @@ import { import { AnonCredsRegistryService } from '../../services/registry/AnonCredsRegistryService' import { LegacyIndyCredentialFormatService } from '../LegacyIndyCredentialFormatService' +indySdk.setDefaultLogger('trace') + const registry = new InMemoryAnonCredsRegistry() const anonCredsModuleConfig = new AnonCredsModuleConfig({ registries: [registry], diff --git a/packages/anoncreds/src/formats/__tests__/LegacyIndyProofFormatService.test.ts b/packages/anoncreds/src/formats/__tests__/LegacyIndyProofFormatService.test.ts new file mode 100644 index 0000000000..ff9a32d81d --- /dev/null +++ b/packages/anoncreds/src/formats/__tests__/LegacyIndyProofFormatService.test.ts @@ -0,0 +1,296 @@ +import { + CredentialState, + CredentialExchangeRecord, + SigningProviderRegistry, + KeyType, + CredentialPreviewAttribute, + ProofExchangeRecord, + ProofState, +} from '@aries-framework/core' +import * as indySdk from 'indy-sdk' + +import { getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { + IndySdkHolderService, + IndySdkIssuerService, + IndySdkVerifierService, + IndySdkWallet, +} from '../../../../indy-sdk/src' +import { IndySdkRevocationService } from '../../../../indy-sdk/src/anoncreds/services/IndySdkRevocationService' +import { indyDidFromPublicKeyBase58 } from '../../../../indy-sdk/src/utils/did' +import { InMemoryAnonCredsRegistry } from '../../../tests/InMemoryAnonCredsRegistry' +import { AnonCredsModuleConfig } from '../../AnonCredsModuleConfig' +import { + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, +} from '../../services' +import { AnonCredsRegistryService } from '../../services/registry/AnonCredsRegistryService' +import { LegacyIndyCredentialFormatService } from '../LegacyIndyCredentialFormatService' +import { LegacyIndyProofFormatService } from '../LegacyIndyProofFormatService' + +const registry = new InMemoryAnonCredsRegistry() +const anonCredsModuleConfig = new AnonCredsModuleConfig({ + registries: [registry], +}) + +const agentConfig = getAgentConfig('LegacyIndyCredentialFormatServiceTest') +const anonCredsRevocationService = new IndySdkRevocationService(indySdk) +const anonCredsVerifierService = new IndySdkVerifierService(indySdk) +const anonCredsHolderService = new IndySdkHolderService(anonCredsRevocationService, indySdk) +const anonCredsIssuerService = new IndySdkIssuerService(indySdk) +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) +const agentContext = getAgentContext({ + registerInstances: [ + [AnonCredsIssuerServiceSymbol, anonCredsIssuerService], + [AnonCredsHolderServiceSymbol, anonCredsHolderService], + [AnonCredsVerifierServiceSymbol, anonCredsVerifierService], + [AnonCredsRegistryService, new AnonCredsRegistryService()], + [AnonCredsModuleConfig, anonCredsModuleConfig], + ], + agentConfig, + wallet, +}) + +const indyCredentialFormatService = new LegacyIndyCredentialFormatService() +const indyProofFormatService = new LegacyIndyProofFormatService() + +// We can split up these tests when we can use AnonCredsRS as a backend, but currently +// we need to have the link secrets etc in the wallet which is not so easy to do with Indy +describe('Legacy indy format services', () => { + beforeEach(async () => { + await wallet.createAndOpen(agentConfig.walletConfig) + }) + + afterEach(async () => { + await wallet.delete() + }) + + test('issuance and verification flow starting from proposal without negotiation and without revocation', async () => { + // This is just so we don't have to register an actually indy did (as we don't have the indy did registrar configured) + const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) + const indyDid = indyDidFromPublicKeyBase58(key.publicKeyBase58) + + const schema = await anonCredsIssuerService.createSchema(agentContext, { + attrNames: ['name', 'age'], + issuerId: indyDid, + name: 'Employee Credential', + version: '1.0.0', + }) + + const { schemaState, schemaMetadata } = await registry.registerSchema(agentContext, { + schema, + options: {}, + }) + + const { credentialDefinition } = await anonCredsIssuerService.createCredentialDefinition( + agentContext, + { + issuerId: indyDid, + schemaId: schemaState.schemaId as string, + schema, + tag: 'Employee Credential', + supportRevocation: false, + }, + { + // Need to pass this as the indy-sdk MUST have the seqNo + indyLedgerSchemaSeqNo: schemaMetadata.indyLedgerSeqNo as number, + } + ) + + const { credentialDefinitionState } = await registry.registerCredentialDefinition(agentContext, { + credentialDefinition, + options: {}, + }) + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + const holderCredentialRecord = new CredentialExchangeRecord({ + protocolVersion: 'v1', + state: CredentialState.ProposalSent, + threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', + }) + + const issuerCredentialRecord = new CredentialExchangeRecord({ + protocolVersion: 'v1', + state: CredentialState.ProposalReceived, + threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', + }) + + const credentialAttributes = [ + new CredentialPreviewAttribute({ + name: 'name', + value: 'John', + }), + new CredentialPreviewAttribute({ + name: 'age', + value: '25', + }), + ] + + // Holder creates proposal + holderCredentialRecord.credentialAttributes = credentialAttributes + const { attachment: proposalAttachment } = await indyCredentialFormatService.createProposal(agentContext, { + credentialRecord: holderCredentialRecord, + credentialFormats: { + indy: { + attributes: credentialAttributes, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }, + }, + }) + + // Issuer processes and accepts proposal + await indyCredentialFormatService.processProposal(agentContext, { + credentialRecord: issuerCredentialRecord, + attachment: proposalAttachment, + }) + // Set attributes on the credential record, this is normally done by the protocol service + issuerCredentialRecord.credentialAttributes = credentialAttributes + const { attachment: offerAttachment } = await indyCredentialFormatService.acceptProposal(agentContext, { + credentialRecord: issuerCredentialRecord, + proposalAttachment: proposalAttachment, + }) + + // Holder processes and accepts offer + await indyCredentialFormatService.processOffer(agentContext, { + credentialRecord: holderCredentialRecord, + attachment: offerAttachment, + }) + const { attachment: requestAttachment } = await indyCredentialFormatService.acceptOffer(agentContext, { + credentialRecord: holderCredentialRecord, + offerAttachment, + }) + + // Issuer processes and accepts request + await indyCredentialFormatService.processRequest(agentContext, { + credentialRecord: issuerCredentialRecord, + attachment: requestAttachment, + }) + const { attachment: credentialAttachment } = await indyCredentialFormatService.acceptRequest(agentContext, { + credentialRecord: issuerCredentialRecord, + requestAttachment, + offerAttachment, + }) + + // Holder processes and accepts credential + await indyCredentialFormatService.processCredential(agentContext, { + credentialRecord: holderCredentialRecord, + attachment: credentialAttachment, + requestAttachment, + }) + + expect(holderCredentialRecord.credentials).toEqual([ + { credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String) }, + ]) + + const credentialId = holderCredentialRecord.credentials[0].credentialRecordId + const anonCredsCredential = await anonCredsHolderService.getCredential(agentContext, { + credentialId, + }) + + expect(anonCredsCredential).toEqual({ + credentialId, + attributes: { + age: '25', + name: 'John', + }, + schemaId: schemaState.schemaId, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + revocationRegistryId: null, + credentialRevocationId: null, + }) + + expect(holderCredentialRecord.metadata.data).toEqual({ + '_anonCreds/anonCredsCredential': { + schemaId: schemaState.schemaId, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }, + '_anonCreds/anonCredsCredentialRequest': { + master_secret_blinding_data: expect.any(Object), + master_secret_name: expect.any(String), + nonce: expect.any(String), + }, + }) + + expect(issuerCredentialRecord.metadata.data).toEqual({ + '_anonCreds/anonCredsCredential': { + schemaId: schemaState.schemaId, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }, + }) + + const holderProofRecord = new ProofExchangeRecord({ + protocolVersion: 'v1', + state: ProofState.ProposalSent, + threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38', + }) + const verifierProofRecord = new ProofExchangeRecord({ + protocolVersion: 'v1', + state: ProofState.ProposalReceived, + threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38', + }) + + const { attachment: proofProposalAttachment } = await indyProofFormatService.createProposal(agentContext, { + proofFormats: { + indy: { + attributes: [ + { + name: 'name', + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + value: 'John', + referent: '1', + }, + ], + predicates: [ + { + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], + name: 'Proof Request', + version: '1.0', + }, + }, + proofRecord: holderProofRecord, + }) + + await indyProofFormatService.processProposal(agentContext, { + attachment: proofProposalAttachment, + proofRecord: verifierProofRecord, + }) + + const { attachment: proofRequestAttachment } = await indyProofFormatService.acceptProposal(agentContext, { + proofRecord: verifierProofRecord, + proposalAttachment: proofProposalAttachment, + }) + + await indyProofFormatService.processRequest(agentContext, { + attachment: proofRequestAttachment, + proofRecord: holderProofRecord, + }) + + const { attachment: proofAttachment } = await indyProofFormatService.acceptRequest(agentContext, { + proofRecord: holderProofRecord, + requestAttachment: proofRequestAttachment, + proposalAttachment: proofProposalAttachment, + }) + + const isValid = await indyProofFormatService.processPresentation(agentContext, { + attachment: proofAttachment, + proofRecord: verifierProofRecord, + requestAttachment: proofRequestAttachment, + }) + + expect(isValid).toBe(true) + }) +}) diff --git a/packages/anoncreds/src/formats/util/__tests__/areRequestsEqual.test.ts b/packages/anoncreds/src/formats/util/__tests__/areRequestsEqual.test.ts new file mode 100644 index 0000000000..f8663f6779 --- /dev/null +++ b/packages/anoncreds/src/formats/util/__tests__/areRequestsEqual.test.ts @@ -0,0 +1,419 @@ +import type { AnonCredsProofRequest } from '../../../models' + +import { areAnonCredsProofRequestsEqual } from '../areRequestsEqual' + +const proofRequest = { + name: 'Proof Request', + version: '1.0.0', + nonce: 'nonce', + ver: '1.0', + non_revoked: {}, + requested_attributes: { + a: { + names: ['name1', 'name2'], + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + schema_id: 'schema_id', + }, + ], + }, + }, + requested_predicates: { + p: { + name: 'Hello', + p_type: '<', + p_value: 10, + restrictions: [ + { + cred_def_id: 'string2', + }, + { + cred_def_id: 'string', + }, + ], + }, + }, +} satisfies AnonCredsProofRequest + +describe('util | areAnonCredsProofRequestsEqual', () => { + test('does not compare name, ver, version and nonce', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + name: 'Proof Request 2', + version: '2.0.0', + nonce: 'nonce2', + ver: '2.0', + }) + ).toBe(true) + }) + + test('check top level non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + non_revoked: {}, + }) + ).toBe(true) + + // properties inside object are different + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + non_revoked: { + to: 5, + }, + }, + { + ...proofRequest, + non_revoked: { + from: 5, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + non_revoked: { + from: 5, + }, + }) + ).toBe(false) + }) + + test('ignores attribute group name differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + b: proofRequest.requested_attributes.a, + }, + }) + ).toBe(true) + }) + + test('ignores attribute restriction order', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [...proofRequest.requested_attributes.a.restrictions].reverse(), + }, + }, + }) + ).toBe(true) + }) + + test('ignores attribute restriction undefined vs empty array', () => { + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: undefined, + }, + }, + }, + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [], + }, + }, + } + ) + ).toBe(true) + }) + + test('ignores attribute names order', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + names: ['name2', 'name1'], + }, + }, + }) + ).toBe(true) + }) + + test('checks attribute non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: {}, + }, + }, + }) + ).toBe(true) + + // properties inside object are different + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + to: 5, + }, + }, + }, + }, + { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + from: 5, + }, + }, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + non_revoked: { + from: 5, + }, + }, + }, + }) + ).toBe(false) + }) + + test('checks attribute restriction differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + cred_def_id: 'cred_def_id2', + }, + ], + }, + }, + }) + ).toBe(false) + }) + + test('checks attribute name differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + names: ['name3'], + }, + }, + }) + ).toBe(false) + + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + a: { + ...proofRequest.requested_attributes.a, + name: 'name3', + names: undefined, + }, + }, + }) + ).toBe(false) + }) + + test('ignores predicate group name differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + a: proofRequest.requested_predicates.p, + }, + }) + ).toBe(true) + }) + + test('ignores predicate restriction order', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [...proofRequest.requested_predicates.p.restrictions].reverse(), + }, + }, + }) + ).toBe(true) + }) + + test('ignores predicate restriction undefined vs empty array', () => { + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: undefined, + }, + }, + }, + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [], + }, + }, + } + ) + ).toBe(true) + }) + + test('checks predicate restriction differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_attributes: { + p: { + ...proofRequest.requested_predicates.p, + restrictions: [ + { + cred_def_id: 'cred_def_id1', + }, + { + cred_def_id: 'cred_def_id2', + }, + ], + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate name differences', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + name: 'name3', + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate non_revocation interval', () => { + // empty object is semantically equal to undefined + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: {}, + }, + }, + }) + ).toBe(true) + + // properties inside object are different + expect( + areAnonCredsProofRequestsEqual( + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + to: 5, + }, + }, + }, + }, + { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + from: 5, + }, + }, + }, + } + ) + ).toBe(false) + + // One has non_revoked, other doesn't + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + non_revoked: { + from: 5, + }, + }, + }, + }) + ).toBe(false) + }) + + test('checks predicate p_type and p_value', () => { + expect( + areAnonCredsProofRequestsEqual(proofRequest, { + ...proofRequest, + requested_predicates: { + p: { + ...proofRequest.requested_predicates.p, + p_type: '<', + p_value: 134134, + }, + }, + }) + ).toBe(false) + }) +}) diff --git a/packages/anoncreds/src/formats/util/__tests__/hasDuplicateGroupNames.test.ts b/packages/anoncreds/src/formats/util/__tests__/hasDuplicateGroupNames.test.ts new file mode 100644 index 0000000000..88872091ba --- /dev/null +++ b/packages/anoncreds/src/formats/util/__tests__/hasDuplicateGroupNames.test.ts @@ -0,0 +1,70 @@ +import type { AnonCredsProofRequest } from '../../../models' + +import { hasDuplicateGroupsNamesInProofRequest } from '../hasDuplicateGroupNames' + +const credentialDefinitionId = '9vPXgSpQJPkJEALbLXueBp:3:CL:57753:tag1' + +describe('util | hasDuplicateGroupsNamesInProofRequest', () => { + describe('assertNoDuplicateGroupsNamesInProofRequest', () => { + test('attribute names match', () => { + const proofRequest = { + name: 'proof-request', + version: '1.0', + nonce: 'testtesttest12345', + requested_attributes: { + age1: { + name: 'age', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + age2: { + name: 'age', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: {}, + } satisfies AnonCredsProofRequest + + expect(hasDuplicateGroupsNamesInProofRequest(proofRequest)).toBe(false) + }) + + test('attribute names match with predicates name', () => { + const proofRequest = { + name: 'proof-request', + version: '1.0', + nonce: 'testtesttest12345', + requested_attributes: { + attrib: { + name: 'age', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + predicate: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + } satisfies AnonCredsProofRequest + + expect(hasDuplicateGroupsNamesInProofRequest(proofRequest)).toBe(true) + }) + }) +}) diff --git a/packages/anoncreds/src/formats/util/__tests__/sortRequestedCredentialsMatches.test.ts b/packages/anoncreds/src/formats/util/__tests__/sortRequestedCredentialsMatches.test.ts new file mode 100644 index 0000000000..3cd296b138 --- /dev/null +++ b/packages/anoncreds/src/formats/util/__tests__/sortRequestedCredentialsMatches.test.ts @@ -0,0 +1,57 @@ +import type { AnonCredsCredentialInfo, AnonCredsRequestedAttributeMatch } from '../../../models' + +import { sortRequestedCredentialsMatches } from '../sortRequestedCredentialsMatches' + +const credentialInfo = {} as unknown as AnonCredsCredentialInfo + +const credentials: AnonCredsRequestedAttributeMatch[] = [ + { + credentialId: '1', + revealed: true, + revoked: true, + credentialInfo, + }, + { + credentialId: '2', + revealed: true, + revoked: undefined, + credentialInfo, + }, + { + credentialId: '3', + revealed: true, + revoked: false, + credentialInfo, + }, + { + credentialId: '4', + revealed: true, + revoked: false, + credentialInfo, + }, + { + credentialId: '5', + revealed: true, + revoked: true, + credentialInfo, + }, + { + credentialId: '6', + revealed: true, + revoked: undefined, + credentialInfo, + }, +] + +describe('sortRequestedCredentialsMatches', () => { + test('sorts the credentials', () => { + expect(sortRequestedCredentialsMatches(credentials)).toEqual([ + credentials[1], + credentials[5], + credentials[2], + credentials[3], + credentials[0], + credentials[4], + ]) + }) +}) diff --git a/packages/anoncreds/src/formats/util/areRequestsEqual.ts b/packages/anoncreds/src/formats/util/areRequestsEqual.ts new file mode 100644 index 0000000000..56bb5d6f5b --- /dev/null +++ b/packages/anoncreds/src/formats/util/areRequestsEqual.ts @@ -0,0 +1,156 @@ +import type { AnonCredsNonRevokedInterval, AnonCredsProofRequest, AnonCredsProofRequestRestriction } from '../../models' + +// Copied from the core package so we don't have to export these silly utils. We should probably move these to a separate package. +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function areObjectsEqual(a: any, b: any): boolean { + if (typeof a == 'object' && a != null && typeof b == 'object' && b != null) { + if (Object.keys(a).length !== Object.keys(b).length) return false + for (const key in a) { + if (!(key in b) || !areObjectsEqual(a[key], b[key])) { + return false + } + } + for (const key in b) { + if (!(key in a) || !areObjectsEqual(b[key], a[key])) { + return false + } + } + return true + } else { + return a === b + } +} + +/** + * Checks whether two `names` arrays are equal. The order of the names doesn't matter. + */ +function areNamesEqual(namesA: string[] | undefined, namesB: string[] | undefined) { + if (namesA === undefined) return namesB === undefined || namesB.length === 0 + if (namesB === undefined) return namesA.length === 0 + + // Check if there are any duplicates + if (new Set(namesA).size !== namesA.length || new Set(namesB).size !== namesB.length) return false + + // Check if the number of names is equal between A & B + if (namesA.length !== namesB.length) return false + + return namesA.every((a) => namesB.includes(a)) +} + +/** + * Checks whether two proof requests are semantically equal. The `name`, `version` and `nonce`, `ver` fields are ignored. + * In addition the group names don't have to be the same between the different requests. + */ +export function areAnonCredsProofRequestsEqual( + requestA: AnonCredsProofRequest, + requestB: AnonCredsProofRequest +): boolean { + // Check if the top-level non-revocation interval is equal + if (!isNonRevokedEqual(requestA.non_revoked, requestB.non_revoked)) return false + + const attributeAList = Object.values(requestA.requested_attributes) + const attributeBList = Object.values(requestB.requested_attributes) + + // Check if the number of attribute groups is equal in both requests + if (attributeAList.length !== attributeBList.length) return false + + // Check if all attribute groups in A are also in B + const attributesMatch = attributeAList.every((a) => { + // find an attribute in B that matches this attribute + const bIndex = attributeBList.findIndex((b) => { + return ( + b.name === a.name && + areNamesEqual(a.names, b.names) && + isNonRevokedEqual(a.non_revoked, b.non_revoked) && + areRestrictionsEqual(a.restrictions, b.restrictions) + ) + }) + + // Match found + if (bIndex !== -1) { + attributeBList.splice(bIndex, 1) + return true + } + + // Match not found + return false + }) + + if (!attributesMatch) return false + + const predicatesA = Object.values(requestA.requested_predicates) + const predicatesB = Object.values(requestB.requested_predicates) + + if (predicatesA.length !== predicatesB.length) return false + const predicatesMatch = predicatesA.every((a) => { + // find a predicate in B that matches this predicate + const bIndex = predicatesB.findIndex((b) => { + return ( + a.name === b.name && + a.p_type === b.p_type && + a.p_value === b.p_value && + isNonRevokedEqual(a.non_revoked, b.non_revoked) && + areRestrictionsEqual(a.restrictions, b.restrictions) + ) + }) + + if (bIndex !== -1) { + predicatesB.splice(bIndex, 1) + return true + } + + return false + }) + + if (!predicatesMatch) return false + + return true +} + +/** + * Checks whether two non-revocation intervals are semantically equal. They are considered equal if: + * - Both are undefined + * - Both are empty objects + * - One if undefined and the other is an empty object + * - Both have the same from and to values + */ +function isNonRevokedEqual( + nonRevokedA: AnonCredsNonRevokedInterval | undefined, + nonRevokedB: AnonCredsNonRevokedInterval | undefined +) { + // Having an empty non-revoked object is the same as not having one + if (nonRevokedA === undefined) + return nonRevokedB === undefined || (nonRevokedB.from === undefined && nonRevokedB.to === undefined) + if (nonRevokedB === undefined) return nonRevokedA.from === undefined && nonRevokedA.to === undefined + + return nonRevokedA.from === nonRevokedB.from && nonRevokedA.to === nonRevokedB.to +} + +/** + * Check if two restriction lists are equal. The order of the restrictions does not matter. + */ +function areRestrictionsEqual( + restrictionsA: AnonCredsProofRequestRestriction[] | undefined, + restrictionsB: AnonCredsProofRequestRestriction[] | undefined +) { + // Having an undefined restrictions property or an empty array is the same + if (restrictionsA === undefined) return restrictionsB === undefined || restrictionsB.length === 0 + if (restrictionsB === undefined) return restrictionsA.length === 0 + + // Clone array to not modify input object + const bList = [...restrictionsB] + + // Check if all restrictions in A are also in B + return restrictionsA.every((a) => { + const bIndex = restrictionsB.findIndex((b) => areObjectsEqual(a, b)) + + // Match found + if (bIndex !== -1) { + bList.splice(bIndex, 1) + return true + } + + // Match not found + return false + }) +} diff --git a/packages/anoncreds/src/formats/util/createRequestFromPreview.ts b/packages/anoncreds/src/formats/util/createRequestFromPreview.ts new file mode 100644 index 0000000000..a24b4196ec --- /dev/null +++ b/packages/anoncreds/src/formats/util/createRequestFromPreview.ts @@ -0,0 +1,89 @@ +import type { AnonCredsProofRequest } from '../../models' +import type { + AnonCredsPresentationPreviewAttribute, + AnonCredsPresentationPreviewPredicate, +} from '../AnonCredsProofFormat' + +import { utils } from '@aries-framework/core' + +export function createRequestFromPreview({ + name, + version, + nonce, + attributes, + predicates, +}: { + name: string + version: string + nonce: string + attributes: AnonCredsPresentationPreviewAttribute[] + predicates: AnonCredsPresentationPreviewPredicate[] +}): AnonCredsProofRequest { + const proofRequest: AnonCredsProofRequest = { + name, + version, + nonce, + requested_attributes: {}, + requested_predicates: {}, + } + + /** + * Create mapping of attributes by referent. This required the + * attributes to come from the same credential. + * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent + * + * { + * "referent1": [Attribute1, Attribute2], + * "referent2": [Attribute3] + * } + */ + const attributesByReferent: Record = {} + for (const proposedAttributes of attributes ?? []) { + const referent = proposedAttributes.referent ?? utils.uuid() + + const referentAttributes = attributesByReferent[referent] + + // Referent key already exist, add to list + if (referentAttributes) { + referentAttributes.push(proposedAttributes) + } + + // Referent key does not exist yet, create new entry + else { + attributesByReferent[referent] = [proposedAttributes] + } + } + + // Transform attributes by referent to requested attributes + for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { + // Either attributeName or attributeNames will be undefined + const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined + const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined + + proofRequest.requested_attributes[referent] = { + name: attributeName, + names: attributeNames, + restrictions: [ + { + cred_def_id: proposedAttributes[0].credentialDefinitionId, + }, + ], + } + } + + // Transform proposed predicates to requested predicates + for (const proposedPredicate of predicates ?? []) { + proofRequest.requested_predicates[utils.uuid()] = { + name: proposedPredicate.name, + p_type: proposedPredicate.predicate, + p_value: proposedPredicate.threshold, + restrictions: [ + { + cred_def_id: proposedPredicate.credentialDefinitionId, + }, + ], + } + } + + return proofRequest +} diff --git a/packages/anoncreds/src/formats/util/hasDuplicateGroupNames.ts b/packages/anoncreds/src/formats/util/hasDuplicateGroupNames.ts new file mode 100644 index 0000000000..64e3f26884 --- /dev/null +++ b/packages/anoncreds/src/formats/util/hasDuplicateGroupNames.ts @@ -0,0 +1,23 @@ +import type { AnonCredsProofRequest } from '../../models' + +function attributeNamesToArray(proofRequest: AnonCredsProofRequest) { + // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array + // containing all attribute names from the requested attributes. + return Object.values(proofRequest.requested_attributes).reduce( + (names, a) => [...names, ...(a.name ? [a.name] : a.names ? a.names : [])], + [] + ) +} + +function predicateNamesToArray(proofRequest: AnonCredsProofRequest) { + return Array.from(new Set(Object.values(proofRequest.requested_predicates).map((a) => a.name))) +} + +// TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. +export function hasDuplicateGroupsNamesInProofRequest(proofRequest: AnonCredsProofRequest) { + const attributes = attributeNamesToArray(proofRequest) + const predicates = predicateNamesToArray(proofRequest) + + const duplicates = predicates.find((item) => attributes.indexOf(item) !== -1) + return duplicates !== undefined +} diff --git a/packages/anoncreds/src/formats/util/index.ts b/packages/anoncreds/src/formats/util/index.ts new file mode 100644 index 0000000000..ff85ceabdd --- /dev/null +++ b/packages/anoncreds/src/formats/util/index.ts @@ -0,0 +1,4 @@ +export { createRequestFromPreview } from './createRequestFromPreview' +export { sortRequestedCredentialsMatches } from './sortRequestedCredentialsMatches' +export { hasDuplicateGroupsNamesInProofRequest } from './hasDuplicateGroupNames' +export { areAnonCredsProofRequestsEqual } from './areRequestsEqual' diff --git a/packages/anoncreds/src/formats/util/sortRequestedCredentialsMatches.ts b/packages/anoncreds/src/formats/util/sortRequestedCredentialsMatches.ts new file mode 100644 index 0000000000..f7568cfc3d --- /dev/null +++ b/packages/anoncreds/src/formats/util/sortRequestedCredentialsMatches.ts @@ -0,0 +1,33 @@ +import type { AnonCredsRequestedAttributeMatch, AnonCredsRequestedPredicateMatch } from '../../models' + +/** + * Sort requested attributes and predicates by `revoked` status. The order is: + * - first credentials with `revoked` set to undefined, this means no revocation status is needed for the credentials + * - then credentials with `revoked` set to false, this means the credentials are not revoked + * - then credentials with `revoked` set to true, this means the credentials are revoked + */ +export function sortRequestedCredentialsMatches< + Requested extends Array | Array +>(credentials: Requested) { + const staySame = 0 + const credentialGoUp = -1 + const credentialGoDown = 1 + + // Clone as sort is in place + const credentialsClone = [...credentials] + + return credentialsClone.sort((credential, compareTo) => { + // Nothing needs to happen if values are the same + if (credential.revoked === compareTo.revoked) return staySame + + // Undefined always is at the top + if (credential.revoked === undefined) return credentialGoUp + if (compareTo.revoked === undefined) return credentialGoDown + + // Then revoked + if (credential.revoked === false) return credentialGoUp + + // It means that compareTo is false and credential is true + return credentialGoDown + }) +} diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index b0e960afb8..5899d82f1f 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -1,3 +1,5 @@ +export type AnonCredsPredicateType = '>=' | '>' | '<=' | '<' + export interface AnonCredsProofRequestRestriction { schema_id?: string schema_issuer_id?: string @@ -62,7 +64,8 @@ export interface AnonCredsProof { encoded: string } > - revealed_attr_groups: Record< + // revealed_attr_groups is only defined if there's a requested attribute using `names` + revealed_attr_groups?: Record< string, { sub_proof_index: number @@ -93,29 +96,27 @@ export interface AnonCredsProof { }> } +export interface AnonCredsRequestedAttribute { + name?: string + names?: string[] + restrictions?: AnonCredsProofRequestRestriction[] + non_revoked?: AnonCredsNonRevokedInterval +} + +export interface AnonCredsRequestedPredicate { + name: string + p_type: AnonCredsPredicateType + p_value: number + restrictions?: AnonCredsProofRequestRestriction[] + non_revoked?: AnonCredsNonRevokedInterval +} + export interface AnonCredsProofRequest { name: string version: string nonce: string - requested_attributes: Record< - string, - { - name?: string - names?: string[] - restrictions?: AnonCredsProofRequestRestriction[] - non_revoked?: AnonCredsNonRevokedInterval - } - > - requested_predicates: Record< - string, - { - name: string - p_type: '>=' | '>' | '<=' | '<' - p_value: number - restrictions?: AnonCredsProofRequestRestriction[] - non_revoked?: AnonCredsNonRevokedInterval - } - > + requested_attributes: Record + requested_predicates: Record non_revoked?: AnonCredsNonRevokedInterval ver?: '1.0' | '2.0' } diff --git a/packages/anoncreds/src/models/internal.ts b/packages/anoncreds/src/models/internal.ts index 27d476ebb3..39452f736a 100644 --- a/packages/anoncreds/src/models/internal.ts +++ b/packages/anoncreds/src/models/internal.ts @@ -9,7 +9,7 @@ export interface AnonCredsCredentialInfo { credentialRevocationId?: string | undefined } -export interface AnonCredsRequestedAttribute { +export interface AnonCredsRequestedAttributeMatch { credentialId: string timestamp?: number revealed: boolean @@ -17,16 +17,16 @@ export interface AnonCredsRequestedAttribute { revoked?: boolean } -export interface AnonCredsRequestedPredicate { +export interface AnonCredsRequestedPredicateMatch { credentialId: string timestamp?: number credentialInfo: AnonCredsCredentialInfo revoked?: boolean } -export interface AnonCredsRequestedCredentials { - requestedAttributes?: Record - requestedPredicates?: Record +export interface AnonCredsSelectedCredentials { + attributes: Record + predicates: Record selfAttestedAttributes: Record } diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index fcbc5e913c..d36a2beb2e 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -1,7 +1,7 @@ import type { AnonCredsCredentialInfo, AnonCredsCredentialRequestMetadata, - AnonCredsRequestedCredentials, + AnonCredsSelectedCredentials, } from '../models' import type { AnonCredsCredential, @@ -24,7 +24,7 @@ export interface AnonCredsAttributeInfo { export interface CreateProofOptions { proofRequest: AnonCredsProofRequest - requestedCredentials: AnonCredsRequestedCredentials + selectedCredentials: AnonCredsSelectedCredentials schemas: { [schemaId: string]: AnonCredsSchema } diff --git a/packages/anoncreds/src/services/AnonCredsVerifierService.ts b/packages/anoncreds/src/services/AnonCredsVerifierService.ts index 00e2a5670d..f0ffdf1e91 100644 --- a/packages/anoncreds/src/services/AnonCredsVerifierService.ts +++ b/packages/anoncreds/src/services/AnonCredsVerifierService.ts @@ -1,9 +1,10 @@ import type { VerifyProofOptions } from './AnonCredsVerifierServiceOptions' +import type { AgentContext } from '@aries-framework/core' export const AnonCredsVerifierServiceSymbol = Symbol('AnonCredsVerifierService') export interface AnonCredsVerifierService { // TODO: do we want to extend the return type with more info besides a boolean. // If the value is false it would be nice to have some extra contexts about why it failed - verifyProof(options: VerifyProofOptions): Promise + verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise } diff --git a/packages/core/src/modules/proofs/models/index.ts b/packages/core/src/modules/proofs/models/index.ts index 9e20094e5e..9dec0e697a 100644 --- a/packages/core/src/modules/proofs/models/index.ts +++ b/packages/core/src/modules/proofs/models/index.ts @@ -1,2 +1,3 @@ export * from './ProofAutoAcceptType' export * from './ProofState' +export * from './ProofFormatSpec' diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index 2e6e63ccc0..d5e82deea7 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -9,7 +9,7 @@ import type { StoreCredentialOptions, GetCredentialsForProofRequestOptions, GetCredentialsForProofRequestReturn, - AnonCredsRequestedCredentials, + AnonCredsSelectedCredentials, AnonCredsCredentialRequestMetadata, CreateLinkSecretOptions, CreateLinkSecretReturn, @@ -76,7 +76,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { } public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { - const { credentialDefinitions, proofRequest, requestedCredentials, schemas } = options + const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options assertIndySdkWallet(agentContext.wallet) @@ -85,7 +85,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { const indyRevocationStates: RevStates = await this.indyRevocationService.createRevocationState( agentContext, proofRequest, - requestedCredentials, + selectedCredentials, options.revocationRegistries ) @@ -117,7 +117,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { const indyProof = await this.indySdk.proverCreateProof( agentContext.wallet.handle, proofRequest as IndyProofRequest, - this.parseRequestedCredentials(requestedCredentials), + this.parseSelectedCredentials(selectedCredentials), agentContext.wallet.masterSecretId, indySchemas, indyCredentialDefinitions, @@ -133,7 +133,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { agentContext.config.logger.error(`Error creating Indy Proof`, { error, proofRequest, - requestedCredentials, + selectedCredentials, }) throw isIndyError(error) ? new IndySdkError(error) : error @@ -338,27 +338,27 @@ export class IndySdkHolderService implements AnonCredsHolderService { } /** - * Converts a public api form of {@link RequestedCredentials} interface into a format {@link Indy.IndyRequestedCredentials} that Indy SDK expects. + * Converts a public api form of {@link AnonCredsSelectedCredentials} interface into a format {@link Indy.IndyRequestedCredentials} that Indy SDK expects. **/ - private parseRequestedCredentials(requestedCredentials: AnonCredsRequestedCredentials): IndyRequestedCredentials { + private parseSelectedCredentials(selectedCredentials: AnonCredsSelectedCredentials): IndyRequestedCredentials { const indyRequestedCredentials: IndyRequestedCredentials = { requested_attributes: {}, requested_predicates: {}, self_attested_attributes: {}, } - for (const groupName in requestedCredentials.requestedAttributes) { + for (const groupName in selectedCredentials.attributes) { indyRequestedCredentials.requested_attributes[groupName] = { - cred_id: requestedCredentials.requestedAttributes[groupName].credentialId, - revealed: requestedCredentials.requestedAttributes[groupName].revealed, - timestamp: requestedCredentials.requestedAttributes[groupName].timestamp, + cred_id: selectedCredentials.attributes[groupName].credentialId, + revealed: selectedCredentials.attributes[groupName].revealed, + timestamp: selectedCredentials.attributes[groupName].timestamp, } } - for (const groupName in requestedCredentials.requestedPredicates) { + for (const groupName in selectedCredentials.predicates) { indyRequestedCredentials.requested_predicates[groupName] = { - cred_id: requestedCredentials.requestedPredicates[groupName].credentialId, - timestamp: requestedCredentials.requestedPredicates[groupName].timestamp, + cred_id: selectedCredentials.predicates[groupName].credentialId, + timestamp: selectedCredentials.predicates[groupName].timestamp, } } diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts index 30f78bcbff..ed2572dee7 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts @@ -2,7 +2,7 @@ import type { AnonCredsRevocationRegistryDefinition, AnonCredsRevocationStatusList, AnonCredsProofRequest, - AnonCredsRequestedCredentials, + AnonCredsSelectedCredentials, AnonCredsCredentialInfo, AnonCredsNonRevokedInterval, } from '@aries-framework/anoncreds' @@ -44,7 +44,7 @@ export class IndySdkRevocationService { public async createRevocationState( agentContext: AgentContext, proofRequest: AnonCredsProofRequest, - requestedCredentials: AnonCredsRequestedCredentials, + selectedCredentials: AnonCredsSelectedCredentials, revocationRegistries: { [revocationRegistryDefinitionId: string]: { // Tails is already downloaded @@ -59,7 +59,7 @@ export class IndySdkRevocationService { try { agentContext.config.logger.debug(`Creating Revocation State(s) for proof request`, { proofRequest, - requestedCredentials, + selectedCredentials, }) const indyRevocationStates: RevStates = {} const referentCredentials: Array<{ @@ -70,18 +70,18 @@ export class IndySdkRevocationService { }> = [] //Retrieve information for referents and push to single array - for (const [referent, requestedCredential] of Object.entries(requestedCredentials.requestedAttributes ?? {})) { + for (const [referent, selectedCredential] of Object.entries(selectedCredentials.attributes ?? {})) { referentCredentials.push({ referent, - credentialInfo: requestedCredential.credentialInfo, + credentialInfo: selectedCredential.credentialInfo, type: RequestReferentType.Attribute, referentRevocationInterval: proofRequest.requested_attributes[referent].non_revoked, }) } - for (const [referent, requestedCredential] of Object.entries(requestedCredentials.requestedPredicates ?? {})) { + for (const [referent, selectedCredential] of Object.entries(selectedCredentials.predicates ?? {})) { referentCredentials.push({ referent, - credentialInfo: requestedCredential.credentialInfo, + credentialInfo: selectedCredential.credentialInfo, type: RequestReferentType.Predicate, referentRevocationInterval: proofRequest.requested_predicates[referent].non_revoked, }) @@ -138,7 +138,7 @@ export class IndySdkRevocationService { agentContext.config.logger.error(`Error creating Indy Revocation State for Proof Request`, { error, proofRequest, - requestedCredentials, + selectedCredentials, }) throw isIndyError(error) ? new IndySdkError(error) : error diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts index 3e76fc6bc9..42ae286038 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts @@ -1,4 +1,5 @@ import type { AnonCredsVerifierService, VerifyProofOptions } from '@aries-framework/anoncreds' +import type { AgentContext } from '@aries-framework/core' import type { CredentialDefs, Schemas, RevocRegDefs, RevRegs, IndyProofRequest } from 'indy-sdk' import { inject, injectable } from '@aries-framework/core' @@ -21,7 +22,7 @@ export class IndySdkVerifierService implements AnonCredsVerifierService { this.indySdk = indySdk } - public async verifyProof(options: VerifyProofOptions): Promise { + public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { try { // The AnonCredsSchema doesn't contain the seqNo anymore. However, the indy credential definition id // does contain the seqNo, so we can extract it from the credential definition id. From 205acf5557d5485b6fc77b0ae85440acdaf1aa66 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 10 Feb 2023 03:10:19 +0100 Subject: [PATCH 04/22] feat: legacy indy proof format service Signed-off-by: Timo Glastra --- packages/anoncreds/package.json | 4 +- .../src/formats/AnonCredsCredentialFormat.ts | 30 +- .../src/formats/LegacyIndyCredentialFormat.ts | 33 +- .../LegacyIndyCredentialFormatService.ts | 50 ++- .../formats/LegacyIndyProofFormatService.ts | 333 ++++++++++++++---- .../LegacyIndyCredentialFormatService.test.ts | 226 ------------ .../src/models/AnonCredsCredentialProposal.ts | 111 ++++++ .../src/models/AnonCredsProofRequest.ts | 83 +++++ .../src/models/AnonCredsRequestedAttribute.ts | 39 ++ .../src/models/AnonCredsRequestedPredicate.ts | 53 +++ .../src/models/AnonCredsRestriction.ts | 139 ++++++++ .../src/models/AnonCredsRevocationInterval.ts | 18 + .../__tests__/AnonCredsRestriction.test.ts | 80 +++++ packages/anoncreds/src/models/exchange.ts | 3 +- .../services/AnonCredsHolderServiceOptions.ts | 2 +- .../AnonCredsVerifierServiceOptions.ts | 2 +- .../__tests__/areRequestsEqual.test.ts | 2 +- .../src/utils/__tests__/credential.test.ts | 8 +- .../__tests__/hasDuplicateGroupNames.test.ts | 2 +- .../__tests__/revocationInterval.test.ts | 37 ++ .../sortRequestedCredentialsMatches.test.ts | 2 +- .../util => utils}/areRequestsEqual.ts | 2 +- .../createRequestFromPreview.ts | 4 +- packages/anoncreds/src/utils/credential.ts | 17 +- .../util => utils}/hasDuplicateGroupNames.ts | 2 +- .../src/{formats/util => utils}/index.ts | 4 + packages/anoncreds/src/utils/isMap.ts | 19 + .../anoncreds/src/utils/revocationInterval.ts | 17 + .../sortRequestedCredentialsMatches.ts | 2 +- packages/anoncreds/src/utils/tails.ts | 57 +++ packages/core/package.json | 1 + packages/core/src/index.ts | 2 +- .../formats/CredentialFormatServiceOptions.ts | 6 +- .../models/CredentialPreviewAttribute.ts | 2 +- .../protocol/v1/V1CredentialProtocol.ts | 10 +- .../v1/messages/V1CredentialPreview.ts | 2 +- .../v2/messages/V2CredentialPreview.ts | 2 +- packages/core/src/storage/FileSystem.ts | 8 +- .../services/IndySdkVerifierService.ts | 8 +- packages/node/src/NodeFileSystem.ts | 26 +- packages/node/tests/NodeFileSystem.test.ts | 29 ++ packages/node/tests/__fixtures__/tailsFile | Bin 0 -> 65666 bytes .../react-native/src/ReactNativeFileSystem.ts | 22 +- 43 files changed, 1097 insertions(+), 402 deletions(-) delete mode 100644 packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts create mode 100644 packages/anoncreds/src/models/AnonCredsCredentialProposal.ts create mode 100644 packages/anoncreds/src/models/AnonCredsProofRequest.ts create mode 100644 packages/anoncreds/src/models/AnonCredsRequestedAttribute.ts create mode 100644 packages/anoncreds/src/models/AnonCredsRequestedPredicate.ts create mode 100644 packages/anoncreds/src/models/AnonCredsRestriction.ts create mode 100644 packages/anoncreds/src/models/AnonCredsRevocationInterval.ts create mode 100644 packages/anoncreds/src/models/__tests__/AnonCredsRestriction.test.ts rename packages/anoncreds/src/{formats/util => utils}/__tests__/areRequestsEqual.test.ts (99%) rename packages/anoncreds/src/{formats/util => utils}/__tests__/hasDuplicateGroupNames.test.ts (96%) create mode 100644 packages/anoncreds/src/utils/__tests__/revocationInterval.test.ts rename packages/anoncreds/src/{formats/util => utils}/__tests__/sortRequestedCredentialsMatches.test.ts (97%) rename packages/anoncreds/src/{formats/util => utils}/areRequestsEqual.ts (98%) rename packages/anoncreds/src/{formats/util => utils}/createRequestFromPreview.ts (96%) rename packages/anoncreds/src/{formats/util => utils}/hasDuplicateGroupNames.ts (94%) rename packages/anoncreds/src/{formats/util => utils}/index.ts (57%) create mode 100644 packages/anoncreds/src/utils/isMap.ts create mode 100644 packages/anoncreds/src/utils/revocationInterval.ts rename packages/anoncreds/src/{formats/util => utils}/sortRequestedCredentialsMatches.ts (97%) create mode 100644 packages/anoncreds/src/utils/tails.ts create mode 100644 packages/node/tests/__fixtures__/tailsFile diff --git a/packages/anoncreds/package.json b/packages/anoncreds/package.json index de4e294a54..37fed497b0 100644 --- a/packages/anoncreds/package.json +++ b/packages/anoncreds/package.json @@ -27,7 +27,9 @@ "dependencies": { "@aries-framework/core": "0.3.3", "@aries-framework/node": "0.3.3", - "bn.js": "^5.2.1" + "bn.js": "^5.2.1", + "class-transformer": "0.5.1", + "class-validator": "0.13.1" }, "devDependencies": { "indy-sdk": "^1.16.0-dev-1636", diff --git a/packages/anoncreds/src/formats/AnonCredsCredentialFormat.ts b/packages/anoncreds/src/formats/AnonCredsCredentialFormat.ts index fd6ebf7fcb..bed6d0ebbd 100644 --- a/packages/anoncreds/src/formats/AnonCredsCredentialFormat.ts +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormat.ts @@ -1,6 +1,21 @@ import type { AnonCredsCredential, AnonCredsCredentialOffer, AnonCredsCredentialRequest } from '../models' import type { CredentialPreviewAttributeOptions, CredentialFormat, LinkedAttachment } from '@aries-framework/core' +export interface AnonCredsCredentialProposalFormat { + schema_issuer_id?: string + schema_name?: string + schema_version?: string + schema_id?: string + + cred_def_id?: string + issuer_id?: string + + // TODO: we don't necessarily need to include these in the AnonCreds Format RFC + // as it's a new one and we can just forbid the use of legacy properties + schema_issuer_did?: string + issuer_did?: string +} + /** * This defines the module payload for calling CredentialsApi.createProposal * or CredentialsApi.negotiateOffer @@ -68,20 +83,7 @@ export interface AnonCredsCredentialFormat extends CredentialFormat { // Format data is based on RFC 0592 // https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments formatData: { - proposal: { - schema_issuer_id?: string - schema_name?: string - schema_version?: string - schema_id?: string - - cred_def_id?: string - issuer_id?: string - - // TODO: we don't necessarily need to include these in the AnonCreds Format RFC - // as it's a new one and we can just forbid the use of legacy properties - schema_issuer_did?: string - issuer_did?: string - } + proposal: AnonCredsCredentialProposalFormat offer: AnonCredsCredentialOffer request: AnonCredsCredentialRequest credential: AnonCredsCredential diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts index ce9be1e3eb..78342fe833 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts @@ -2,10 +2,18 @@ import type { AnonCredsAcceptOfferFormat, AnonCredsAcceptProposalFormat, AnonCredsAcceptRequestFormat, + AnonCredsCredentialProposalFormat, AnonCredsOfferCredentialFormat, + AnonCredsProposeCredentialFormat, } from './AnonCredsCredentialFormat' import type { AnonCredsCredential, AnonCredsCredentialOffer, AnonCredsCredentialRequest } from '../models' -import type { CredentialPreviewAttributeOptions, CredentialFormat, LinkedAttachment } from '@aries-framework/core' +import type { CredentialFormat } from '@aries-framework/core' + +// Legacy indy credential proposal doesn't support _id properties +export type LegacyIndyCredentialProposalFormat = Omit< + AnonCredsCredentialProposalFormat, + 'schema_issuer_id' | 'issuer_id' +> /** * This defines the module payload for calling CredentialsApi.createProposal @@ -13,18 +21,7 @@ import type { CredentialPreviewAttributeOptions, CredentialFormat, LinkedAttachm * * NOTE: This doesn't include the `issuerId` and `schemaIssuerId` properties that are present in the newer format. */ -export interface LegacyIndyProposeCredentialFormat { - schemaIssuerDid?: string - schemaId?: string - schemaName?: string - schemaVersion?: string - - credentialDefinitionId?: string - issuerDid?: string - - attributes?: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] -} +type LegacyIndyProposeCredentialFormat = Omit export interface LegacyIndyCredentialRequest extends AnonCredsCredentialRequest { // prover_did is optional in AnonCreds credential request, but required in legacy format @@ -51,15 +48,7 @@ export interface LegacyIndyCredentialFormat extends CredentialFormat { // Format data is based on RFC 0592 // https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments formatData: { - proposal: { - schema_name?: string - schema_issuer_did?: string - schema_version?: string - schema_id?: string - - cred_def_id?: string - issuer_did?: string - } + proposal: LegacyIndyCredentialProposalFormat offer: AnonCredsCredentialOffer request: LegacyIndyCredentialRequest credential: AnonCredsCredential diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts index 3ae72ed704..6bc8a9a33e 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -1,4 +1,4 @@ -import type { LegacyIndyCredentialFormat } from './LegacyIndyCredentialFormat' +import type { LegacyIndyCredentialFormat, LegacyIndyCredentialProposalFormat } from './LegacyIndyCredentialFormat' import type { AnonCredsCredential, AnonCredsCredentialOffer, @@ -30,21 +30,19 @@ import type { } from '@aries-framework/core' import { + MessageValidator, CredentialFormatSpec, AriesFrameworkError, - IndyCredPropose, - JsonTransformer, Attachment, - CredentialPreviewAttribute, - AttachmentData, JsonEncoder, utils, - MessageValidator, CredentialProblemReportError, CredentialProblemReportReason, + JsonTransformer, } from '@aries-framework/core' import { AnonCredsError } from '../error' +import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' import { AnonCredsIssuerServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' import { @@ -96,8 +94,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic // The easiest way is to destructure and use the spread operator. But that leaves the other properties unused // eslint-disable-next-line @typescript-eslint/no-unused-vars const { attributes, linkedAttachments, ...indyCredentialProposal } = indyFormat - - const proposal = new IndyCredPropose(indyCredentialProposal) + const proposal = new AnonCredsCredentialProposal(indyCredentialProposal) try { MessageValidator.validateSync(proposal) @@ -105,8 +102,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic throw new AriesFrameworkError(`Invalid proposal supplied: ${indyCredentialProposal} in Indy Format Service`) } - const proposalJson = JsonTransformer.toJSON(proposal) - const attachment = this.getFormatData(proposalJson, format.attachmentId) + const attachment = this.getFormatData(JsonTransformer.toJSON(proposal), format.attachmentId) const { previewAttributes } = this.getCredentialLinkedAttachments( indyFormat.attributes, @@ -128,8 +124,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic ): Promise { const proposalJson = attachment.getDataAsJson() - // fromJSON also validates - JsonTransformer.fromJSON(proposalJson, IndyCredPropose) + JsonTransformer.fromJSON(proposalJson, AnonCredsCredentialProposal) } public async acceptProposal( @@ -143,9 +138,8 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic ): Promise { const indyFormat = credentialFormats?.indy - const credentialProposal = JsonTransformer.fromJSON(proposalAttachment.getDataAsJson(), IndyCredPropose) - - const credentialDefinitionId = indyFormat?.credentialDefinitionId ?? credentialProposal.credentialDefinitionId + const proposalJson = proposalAttachment.getDataAsJson() + const credentialDefinitionId = indyFormat?.credentialDefinitionId ?? proposalJson.cred_def_id const attributes = indyFormat?.attributes ?? credentialRecord.credentialAttributes @@ -447,30 +441,26 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic agentContext: AgentContext, { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondProposalOptions ) { - const credentialProposalJson = proposalAttachment.getDataAsJson() - const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) - - const credentialOfferJson = offerAttachment.getDataAsJson() + const proposalJson = proposalAttachment.getDataAsJson() + const offerJson = offerAttachment.getDataAsJson() // We want to make sure the credential definition matches. // TODO: If no credential definition is present on the proposal, we could check whether the other fields // of the proposal match with the credential definition id. - return credentialProposal.credentialDefinitionId === credentialOfferJson.cred_def_id + return proposalJson.cred_def_id === offerJson.cred_def_id } public async shouldAutoRespondToOffer( agentContext: AgentContext, { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondOfferOptions ) { - const credentialProposalJson = proposalAttachment.getDataAsJson() - const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) - - const credentialOfferJson = offerAttachment.getDataAsJson() + const proposalJson = proposalAttachment.getDataAsJson() + const offerJson = offerAttachment.getDataAsJson() // We want to make sure the credential definition matches. // TODO: If no credential definition is present on the proposal, we could check whether the other fields // of the proposal match with the credential definition id. - return credentialProposal.credentialDefinitionId === credentialOfferJson.cred_def_id + return proposalJson.cred_def_id === offerJson.cred_def_id } public async shouldAutoRespondToRequest( @@ -550,7 +540,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic private async assertPreviewAttributesMatchSchemaAttributes( agentContext: AgentContext, offer: AnonCredsCredentialOffer, - attributes: CredentialPreviewAttribute[] + attributes: CredentialPreviewAttributeOptions[] ): Promise { const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) const registry = registryService.getRegistryForIdentifier(agentContext, offer.schema_id) @@ -578,13 +568,13 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic linkedAttachments?: LinkedAttachment[] ): { attachments?: Attachment[] - previewAttributes?: CredentialPreviewAttribute[] + previewAttributes?: CredentialPreviewAttributeOptions[] } { if (!linkedAttachments && !attributes) { return {} } - let previewAttributes = attributes?.map((attribute) => new CredentialPreviewAttribute(attribute)) ?? [] + let previewAttributes = attributes ?? [] let attachments: Attachment[] | undefined if (linkedAttachments) { @@ -608,9 +598,9 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic const attachment = new Attachment({ id, mimeType: 'application/json', - data: new AttachmentData({ + data: { base64: JsonEncoder.toBase64(data), - }), + }, }) return attachment diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts index 86186e2c3c..5b66162fbf 100644 --- a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -5,16 +5,23 @@ import type { import type { LegacyIndyProofFormat } from './LegacyIndyProofFormat' import type { AnonCredsCredentialDefinition, + AnonCredsCredentialInfo, AnonCredsProof, - AnonCredsProofRequest, AnonCredsRequestedAttribute, AnonCredsRequestedAttributeMatch, AnonCredsRequestedPredicate, AnonCredsRequestedPredicateMatch, AnonCredsSchema, AnonCredsSelectedCredentials, + AnonCredsProofRequest, } from '../models' -import type { AnonCredsHolderService, AnonCredsVerifierService, GetCredentialsForProofRequestReturn } from '../services' +import type { + AnonCredsHolderService, + AnonCredsVerifierService, + CreateProofOptions, + GetCredentialsForProofRequestReturn, + VerifyProofOptions, +} from '../services' import type { ProofFormatService, AgentContext, @@ -34,19 +41,28 @@ import type { IndyGetCredentialsForProofRequestOptions, } from '@aries-framework/core' -import { AriesFrameworkError, Attachment, AttachmentData, JsonEncoder, ProofFormatSpec } from '@aries-framework/core' +import { + AriesFrameworkError, + Attachment, + AttachmentData, + JsonEncoder, + ProofFormatSpec, + JsonTransformer, +} from '@aries-framework/core' +import { AnonCredsProofRequest as AnonCredsProofRequestClass } from '../models/AnonCredsProofRequest' import { AnonCredsVerifierServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' -// TODO: unify utils -import { checkValidEncoding, encode } from '../utils/credential' - import { sortRequestedCredentialsMatches, createRequestFromPreview, hasDuplicateGroupsNamesInProofRequest, areAnonCredsProofRequestsEqual, -} from './util' + assertRevocationInterval, + downloadTailsFile, + checkValidCredentialValueEncoding, + encodeCredentialValue, +} from '../utils' const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' @@ -82,9 +98,11 @@ export class LegacyIndyProofFormatService implements ProofFormatService { - // TODO: validation const proposalJson = attachment.getDataAsJson() + // fromJson also validates + JsonTransformer.fromJSON(proposalJson, AnonCredsProofRequestClass) + // Assert attribute and predicate (group) names do not match if (hasDuplicateGroupsNamesInProofRequest(proposalJson)) { throw new AriesFrameworkError('Attribute and predicate (group) names must be unique in proof request') @@ -147,9 +165,11 @@ export class LegacyIndyProofFormatService implements ProofFormatService { - // TODO: validation const requestJson = attachment.getDataAsJson() + // fromJson also validates + JsonTransformer.fromJSON(requestJson, AnonCredsProofRequestClass) + // Assert attribute and predicate (group) names do not match if (hasDuplicateGroupsNamesInProofRequest(requestJson)) { throw new AriesFrameworkError('Attribute and predicate (group) names must be unique in proof request') @@ -192,14 +212,17 @@ export class LegacyIndyProofFormatService implements ProofFormatService() - // TODO: validation + // NOTE: we don't do validation here, as this is handled by the AnonCreds implementation, however + // this can lead to confusing error messages. We should consider doing validation here as well. + // Defining a class-transformer/class-validator class seems a bit overkill, and the usage of interfaces + // for the anoncreds package keeps things simple. Maybe we can try to use something like zod to validate const proofJson = attachment.getDataAsJson() for (const [referent, attribute] of Object.entries(proofJson.requested_proof.revealed_attrs)) { - if (!checkValidEncoding(attribute.raw, attribute.encoded)) { + if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { throw new AriesFrameworkError( `The encoded value for '${referent}' is invalid. ` + - `Expected '${encode(attribute.raw)}'. ` + + `Expected '${encodeCredentialValue(attribute.raw)}'. ` + `Actual '${attribute.encoded}'` ) } @@ -207,10 +230,10 @@ export class LegacyIndyProofFormatService implements ProofFormatService i.cred_def_id)) ) + const revocationRegistries = await this.getRevocationRegistriesForProof(agentContext, proofJson) + return await verifierService.verifyProof(agentContext, { proofRequest: proofRequestJson, proof: proofJson, schemas, credentialDefinitions, - // TODO: revocation registry definitions - revocationStates: {}, + revocationRegistries, }) } @@ -322,18 +346,19 @@ export class LegacyIndyProofFormatService implements ProofFormatService { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { + const { isRevoked, timestamp } = await this.getRevocationStatus( + agentContext, proofRequest, - requestedItem: requestedAttribute, - credential, - }) + requestedAttribute, + credential.credentialInfo + ) return { credentialId: credential.credentialInfo.credentialId, revealed: true, credentialInfo: credential.credentialInfo, - timestamp: deltaTimestamp, - revoked, + timestamp, + revoked: isRevoked, } satisfies AnonCredsRequestedAttributeMatch }) ) @@ -354,17 +379,18 @@ export class LegacyIndyProofFormatService implements ProofFormatService { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { + const { isRevoked, timestamp } = await this.getRevocationStatus( + agentContext, proofRequest, - requestedItem: requestedPredicate, - credential, - }) + requestedPredicate, + credential.credentialInfo + ) return { credentialId: credential.credentialInfo.credentialId, credentialInfo: credential.credentialInfo, - timestamp: deltaTimestamp, - revoked, + timestamp, + revoked: isRevoked, } satisfies AnonCredsRequestedPredicateMatch }) ) @@ -496,6 +522,59 @@ export class LegacyIndyProofFormatService implements ProofFormatService c.credentialDefinitionId)) ) + const revocationRegistries = await this.getRevocationRegistriesForRequest( + agentContext, + proofRequest, + selectedCredentials + ) + return await holderService.createProof(agentContext, { proofRequest, selectedCredentials, schemas, credentialDefinitions, - // TODO: resolve and pass revocation registries - revocationRegistries: {}, + revocationRegistries, }) } - private async getRevocationStatusForRequestedItem( + private async getRevocationRegistriesForRequest( agentContext: AgentContext, - { - proofRequest, - requestedItem, - credential, - }: { - proofRequest: AnonCredsProofRequest - requestedItem: AnonCredsRequestedAttribute | AnonCredsRequestedPredicate - credential: GetCredentialsForProofRequestReturn[number] - } + proofRequest: AnonCredsProofRequest, + selectedCredentials: AnonCredsSelectedCredentials ) { - // const indyRevocationService = agentContext.dependencyManager.resolve(IndyRevocationService) + const revocationRegistries: CreateProofOptions['revocationRegistries'] = {} + + try { + agentContext.config.logger.debug(`Retrieving revocation registries for proof request`, { + proofRequest, + selectedCredentials, + }) + + const referentCredentials = [] + + // Retrieve information for referents and push to single array + for (const [referent, selectedCredential] of Object.entries(selectedCredentials.attributes)) { + referentCredentials.push({ + referent, + credentialInfo: selectedCredential.credentialInfo, + nonRevoked: proofRequest.requested_attributes[referent].non_revoked ?? proofRequest.non_revoked, + }) + } + for (const [referent, selectedCredential] of Object.entries(selectedCredentials.predicates)) { + referentCredentials.push({ + referent, + credentialInfo: selectedCredential.credentialInfo, + nonRevoked: proofRequest.requested_predicates[referent].non_revoked ?? proofRequest.non_revoked, + }) + } - const requestNonRevoked = requestedItem.non_revoked ?? proofRequest.non_revoked - const credentialRevocationId = credential.credentialInfo.credentialRevocationId - const revocationRegistryId = credential.credentialInfo.revocationRegistryId - - // If revocation interval is present and the credential is revocable then fetch the revocation status of credentials for display - if (requestNonRevoked && credentialRevocationId && revocationRegistryId) { - agentContext.config.logger.trace( - `Presentation is requesting proof of non revocation, getting revocation status for credential`, - { - requestNonRevoked, - credentialRevocationId, - revocationRegistryId, + for (const { referent, credentialInfo, nonRevoked } of referentCredentials) { + if (!credentialInfo) { + throw new AriesFrameworkError( + `Credential for referent '${referent} does not have credential info for revocation state creation` + ) } - ) - // TODO: update this to use the anoncreds registry - // Note presentation from-to's vs ledger from-to's: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals - // const status = await indyRevocationService.getRevocationStatus( - // agentContext, - // credentialRevocationId, - // revocationRegistryId, - // requestNonRevoked - // ) + // Prefer referent-specific revocation interval over global revocation interval + const credentialRevocationId = credentialInfo.credentialRevocationId + const revocationRegistryId = credentialInfo.revocationRegistryId + + // If revocation interval is present and the credential is revocable then create revocation state + if (nonRevoked && credentialRevocationId && revocationRegistryId) { + agentContext.config.logger.trace( + `Presentation is requesting proof of non revocation for referent '${referent}', creating revocation state for credential`, + { + nonRevoked, + credentialRevocationId, + revocationRegistryId, + } + ) + + // Make sure the revocation interval follows best practices from Aries RFC 0441 + assertRevocationInterval(nonRevoked) + + const registry = agentContext.dependencyManager + .resolve(AnonCredsRegistryService) + .getRegistryForIdentifier(agentContext, revocationRegistryId) + + // Fetch revocation registry definition if not in revocation registries list yet + if (!revocationRegistries[revocationRegistryId]) { + const { revocationRegistryDefinition, resolutionMetadata } = await registry.getRevocationRegistryDefinition( + agentContext, + revocationRegistryId + ) + if (!revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Could not retrieve revocation registry definition for revocation registry ${revocationRegistryId}: ${resolutionMetadata.message}` + ) + } + + const { tailsLocation, tailsHash } = revocationRegistryDefinition + const { tailsFilePath } = await downloadTailsFile(agentContext, tailsLocation, tailsHash) + + // const tails = await this.indyUtilitiesService.downloadTails(tailsHash, tailsLocation) + revocationRegistries[revocationRegistryId] = { + definition: revocationRegistryDefinition, + tailsFilePath, + revocationStatusLists: {}, + } + } + + // TODO: can we check if the revocation status list is already fetched? We don't know which timestamp the query will return. This + // should probably be solved using caching + // Fetch the revocation status list + const { revocationStatusList, resolutionMetadata: statusListResolutionMetadata } = + await registry.getRevocationStatusList(agentContext, revocationRegistryId, nonRevoked.to ?? Date.now()) + if (!revocationStatusList) { + throw new AriesFrameworkError( + `Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${statusListResolutionMetadata.message}` + ) + } + + revocationRegistries[revocationRegistryId].revocationStatusLists[revocationStatusList.timestamp] = + revocationStatusList + } + } + + agentContext.config.logger.debug(`Retrieved revocation registries for proof request`, { + revocationRegistries, + }) + + return revocationRegistries + } catch (error) { + agentContext.config.logger.error(`Error retrieving revocation registry for proof request`, { + error, + proofRequest, + selectedCredentials, + }) + + throw error + } + } - return { status: undefined, deltaTimestamp: undefined } + private async getRevocationRegistriesForProof(agentContext: AgentContext, proof: AnonCredsProof) { + const revocationRegistries: VerifyProofOptions['revocationRegistries'] = {} + + for (const identifier of proof.identifiers) { + const revocationRegistryId = identifier.rev_reg_id + const timestamp = identifier.timestamp + + // Skip if no revocation registry id is present + if (!revocationRegistryId || !timestamp) continue + + const registry = agentContext.dependencyManager + .resolve(AnonCredsRegistryService) + .getRegistryForIdentifier(agentContext, revocationRegistryId) + + // Fetch revocation registry definition if not already fetched + if (!revocationRegistries[revocationRegistryId]) { + const { revocationRegistryDefinition, resolutionMetadata } = await registry.getRevocationRegistryDefinition( + agentContext, + revocationRegistryId + ) + if (!revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Could not retrieve revocation registry definition for revocation registry ${revocationRegistryId}: ${resolutionMetadata.message}` + ) + } + + revocationRegistries[revocationRegistryId] = { + definition: revocationRegistryDefinition, + revocationStatusLists: {}, + } + } + + // Fetch revocation status list by timestamp if not already fetched + if (!revocationRegistries[revocationRegistryId].revocationStatusLists[timestamp]) { + const { revocationStatusList, resolutionMetadata: statusListResolutionMetadata } = + await registry.getRevocationStatusList(agentContext, revocationRegistryId, timestamp) + + if (!revocationStatusList) { + throw new AriesFrameworkError( + `Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${statusListResolutionMetadata.message}` + ) + } + + revocationRegistries[revocationRegistryId].revocationStatusLists[timestamp] = revocationStatusList + } } - return { revoked: undefined, deltaTimestamp: undefined } + return revocationRegistries } /** diff --git a/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts b/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts deleted file mode 100644 index 6641ea2de8..0000000000 --- a/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { - CredentialState, - CredentialExchangeRecord, - SigningProviderRegistry, - KeyType, - CredentialPreviewAttribute, -} from '@aries-framework/core' -import * as indySdk from 'indy-sdk' - -import { getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' -import { - IndySdkHolderService, - IndySdkIssuerService, - IndySdkVerifierService, - IndySdkWallet, -} from '../../../../indy-sdk/src' -import { IndySdkRevocationService } from '../../../../indy-sdk/src/anoncreds/services/IndySdkRevocationService' -import { indyDidFromPublicKeyBase58 } from '../../../../indy-sdk/src/utils/did' -import { InMemoryAnonCredsRegistry } from '../../../tests/InMemoryAnonCredsRegistry' -import { AnonCredsModuleConfig } from '../../AnonCredsModuleConfig' -import { - AnonCredsHolderServiceSymbol, - AnonCredsIssuerServiceSymbol, - AnonCredsVerifierServiceSymbol, -} from '../../services' -import { AnonCredsRegistryService } from '../../services/registry/AnonCredsRegistryService' -import { LegacyIndyCredentialFormatService } from '../LegacyIndyCredentialFormatService' - -indySdk.setDefaultLogger('trace') - -const registry = new InMemoryAnonCredsRegistry() -const anonCredsModuleConfig = new AnonCredsModuleConfig({ - registries: [registry], -}) - -const agentConfig = getAgentConfig('LegacyIndyCredentialFormatServiceTest') -const anonCredsRevocationService = new IndySdkRevocationService(indySdk) -const anonCredsVerifierService = new IndySdkVerifierService(indySdk) -const anonCredsHolderService = new IndySdkHolderService(anonCredsRevocationService, indySdk) -const anonCredsIssuerService = new IndySdkIssuerService(indySdk) -const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) -const agentContext = getAgentContext({ - registerInstances: [ - [AnonCredsIssuerServiceSymbol, anonCredsIssuerService], - [AnonCredsHolderServiceSymbol, anonCredsHolderService], - [AnonCredsVerifierServiceSymbol, anonCredsVerifierService], - [AnonCredsRegistryService, new AnonCredsRegistryService()], - [AnonCredsModuleConfig, anonCredsModuleConfig], - ], - agentConfig, - wallet, -}) - -const indyCredentialFormatService = new LegacyIndyCredentialFormatService() - -describe('LegacyIndyCredentialFormatService', () => { - beforeEach(async () => { - await wallet.createAndOpen(agentConfig.walletConfig) - }) - - afterEach(async () => { - await wallet.delete() - }) - - test('issuance flow starting from proposal without negotiation and without revocation', async () => { - // This is just so we don't have to register an actually indy did (as we don't have the indy did registrar configured) - const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) - const indyDid = indyDidFromPublicKeyBase58(key.publicKeyBase58) - - const schema = await anonCredsIssuerService.createSchema(agentContext, { - attrNames: ['name', 'age'], - issuerId: indyDid, - name: 'Employee Credential', - version: '1.0.0', - }) - - const { schemaState, schemaMetadata } = await registry.registerSchema(agentContext, { - schema, - options: {}, - }) - - const { credentialDefinition } = await anonCredsIssuerService.createCredentialDefinition( - agentContext, - { - issuerId: indyDid, - schemaId: schemaState.schemaId as string, - schema, - tag: 'Employee Credential', - supportRevocation: false, - }, - { - // Need to pass this as the indy-sdk MUST have the seqNo - indyLedgerSchemaSeqNo: schemaMetadata.indyLedgerSeqNo as number, - } - ) - - const { credentialDefinitionState } = await registry.registerCredentialDefinition(agentContext, { - credentialDefinition, - options: {}, - }) - - if ( - !credentialDefinitionState.credentialDefinition || - !credentialDefinitionState.credentialDefinitionId || - !schemaState.schema || - !schemaState.schemaId - ) { - throw new Error('Failed to create schema or credential definition') - } - - const holderCredentialRecord = new CredentialExchangeRecord({ - protocolVersion: 'v1', - state: CredentialState.ProposalSent, - threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', - }) - - const issuerCredentialRecord = new CredentialExchangeRecord({ - protocolVersion: 'v1', - state: CredentialState.ProposalReceived, - threadId: 'f365c1a5-2baf-4873-9432-fa87c888a0aa', - }) - - const credentialAttributes = [ - new CredentialPreviewAttribute({ - name: 'name', - value: 'John', - }), - new CredentialPreviewAttribute({ - name: 'age', - value: '25', - }), - ] - - // Holder creates proposal - holderCredentialRecord.credentialAttributes = credentialAttributes - const { attachment: proposalAttachment } = await indyCredentialFormatService.createProposal(agentContext, { - credentialRecord: holderCredentialRecord, - credentialFormats: { - indy: { - attributes: credentialAttributes, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, - }, - }, - }) - - // Issuer processes and accepts proposal - await indyCredentialFormatService.processProposal(agentContext, { - credentialRecord: issuerCredentialRecord, - attachment: proposalAttachment, - }) - // Set attributes on the credential record, this is normally done by the protocol service - issuerCredentialRecord.credentialAttributes = credentialAttributes - const { attachment: offerAttachment } = await indyCredentialFormatService.acceptProposal(agentContext, { - credentialRecord: issuerCredentialRecord, - proposalAttachment: proposalAttachment, - }) - - // Holder processes and accepts offer - await indyCredentialFormatService.processOffer(agentContext, { - credentialRecord: holderCredentialRecord, - attachment: offerAttachment, - }) - const { attachment: requestAttachment } = await indyCredentialFormatService.acceptOffer(agentContext, { - credentialRecord: holderCredentialRecord, - offerAttachment, - }) - - // Issuer processes and accepts request - await indyCredentialFormatService.processRequest(agentContext, { - credentialRecord: issuerCredentialRecord, - attachment: requestAttachment, - }) - const { attachment: credentialAttachment } = await indyCredentialFormatService.acceptRequest(agentContext, { - credentialRecord: issuerCredentialRecord, - requestAttachment, - offerAttachment, - }) - - // Holder processes and accepts credential - await indyCredentialFormatService.processCredential(agentContext, { - credentialRecord: holderCredentialRecord, - attachment: credentialAttachment, - requestAttachment, - }) - - expect(holderCredentialRecord.credentials).toEqual([ - { credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String) }, - ]) - - const credentialId = holderCredentialRecord.credentials[0].credentialRecordId - const anonCredsCredential = await anonCredsHolderService.getCredential(agentContext, { - credentialId, - }) - - expect(anonCredsCredential).toEqual({ - credentialId, - attributes: { - age: '25', - name: 'John', - }, - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, - revocationRegistryId: null, - credentialRevocationId: null, - }) - - expect(holderCredentialRecord.metadata.data).toEqual({ - '_anonCreds/anonCredsCredential': { - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, - }, - '_anonCreds/anonCredsCredentialRequest': { - master_secret_blinding_data: expect.any(Object), - master_secret_name: expect.any(String), - nonce: expect.any(String), - }, - }) - - expect(issuerCredentialRecord.metadata.data).toEqual({ - '_anonCreds/anonCredsCredential': { - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, - }, - }) - }) -}) diff --git a/packages/anoncreds/src/models/AnonCredsCredentialProposal.ts b/packages/anoncreds/src/models/AnonCredsCredentialProposal.ts new file mode 100644 index 0000000000..928c26b5d5 --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsCredentialProposal.ts @@ -0,0 +1,111 @@ +import { Expose } from 'class-transformer' +import { IsOptional, IsString } from 'class-validator' + +export interface AnonCredsCredentialProposalOptions { + /** + * @deprecated Use `schemaIssuerId` instead. Only valid for legacy indy identifiers. + */ + schemaIssuerDid?: string + schemaIssuerId?: string + + schemaId?: string + schemaName?: string + schemaVersion?: string + credentialDefinitionId?: string + + /** + * @deprecated Use `issuerId` instead. Only valid for legacy indy identifiers. + */ + issuerDid?: string + issuerId?: string +} + +/** + * Class representing an AnonCreds credential proposal as defined in Aries RFC 0592 (and soon the new AnonCreds RFC) + */ +export class AnonCredsCredentialProposal { + public constructor(options: AnonCredsCredentialProposalOptions) { + if (options) { + this.schemaIssuerDid = options.schemaIssuerDid + this.schemaIssuerId = options.schemaIssuerId + this.schemaId = options.schemaId + this.schemaName = options.schemaName + this.schemaVersion = options.schemaVersion + this.credentialDefinitionId = options.credentialDefinitionId + this.issuerDid = options.issuerDid + this.issuerId = options.issuerId + } + } + + /** + * Filter to request credential based on a particular Schema issuer DID. + * + * May only be used with legacy indy identifiers + * + * @deprecated Use schemaIssuerId instead + */ + @Expose({ name: 'schema_issuer_did' }) + @IsString() + @IsOptional() + public schemaIssuerDid?: string + + /** + * Filter to request credential based on a particular Schema issuer DID. + */ + @Expose({ name: 'schema_issuer_id' }) + @IsString() + @IsOptional() + public schemaIssuerId?: string + + /** + * Filter to request credential based on a particular Schema. + */ + @Expose({ name: 'schema_id' }) + @IsString() + @IsOptional() + public schemaId?: string + + /** + * Filter to request credential based on a schema name. + */ + @Expose({ name: 'schema_name' }) + @IsString() + @IsOptional() + public schemaName?: string + + /** + * Filter to request credential based on a schema version. + */ + @Expose({ name: 'schema_version' }) + @IsString() + @IsOptional() + public schemaVersion?: string + + /** + * Filter to request credential based on a particular Credential Definition. + */ + @Expose({ name: 'cred_def_id' }) + @IsString() + @IsOptional() + public credentialDefinitionId?: string + + /** + * Filter to request a credential issued by the owner of a particular DID. + * + * May only be used with legacy indy identifiers + * + * @deprecated Use issuerId instead + */ + @Expose({ name: 'issuer_did' }) + @IsString() + @IsOptional() + public issuerDid?: string + + /** + * Filter to request a credential issued by the owner of a particular DID. + */ + @Expose({ name: 'issuer_id' }) + @IsString() + @IsOptional() + public issuerId?: string +} diff --git a/packages/anoncreds/src/models/AnonCredsProofRequest.ts b/packages/anoncreds/src/models/AnonCredsProofRequest.ts new file mode 100644 index 0000000000..34abfe3030 --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsProofRequest.ts @@ -0,0 +1,83 @@ +import type { AnonCredsRequestedPredicateOptions } from './AnonCredsRequestedPredicate' + +import { IndyRevocationInterval } from '@aries-framework/core' +import { Expose, Type } from 'class-transformer' +import { IsIn, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { IsMap } from '../utils' + +import { AnonCredsRequestedAttribute } from './AnonCredsRequestedAttribute' +import { AnonCredsRequestedPredicate } from './AnonCredsRequestedPredicate' +import { AnonCredsRevocationInterval } from './AnonCredsRevocationInterval' + +export interface AnonCredsProofRequestOptions { + name: string + version: string + nonce: string + nonRevoked?: AnonCredsRevocationInterval + ver?: '1.0' | '2.0' + requestedAttributes?: Record + requestedPredicates?: Record +} + +/** + * Proof Request for AnonCreds based proof format + */ +export class AnonCredsProofRequest { + public constructor(options: AnonCredsProofRequestOptions) { + if (options) { + this.name = options.name + this.version = options.version + this.nonce = options.nonce + + this.requestedAttributes = new Map( + Object.entries(options.requestedAttributes ?? {}).map(([key, attribute]) => [ + key, + new AnonCredsRequestedAttribute(attribute), + ]) + ) + + this.requestedPredicates = new Map( + Object.entries(options.requestedPredicates ?? {}).map(([key, predicate]) => [ + key, + new AnonCredsRequestedPredicate(predicate), + ]) + ) + + this.nonRevoked = options.nonRevoked ? new AnonCredsRevocationInterval(options.nonRevoked) : undefined + this.ver = options.ver + } + } + + @IsString() + public name!: string + + @IsString() + public version!: string + + @IsString() + public nonce!: string + + @Expose({ name: 'requested_attributes' }) + @IsMap() + @ValidateNested({ each: true }) + @Type(() => AnonCredsRequestedAttribute) + public requestedAttributes!: Map + + @Expose({ name: 'requested_predicates' }) + @IsMap() + @ValidateNested({ each: true }) + @Type(() => AnonCredsRequestedPredicate) + public requestedPredicates!: Map + + @Expose({ name: 'non_revoked' }) + @ValidateNested() + @Type(() => IndyRevocationInterval) + @IsOptional() + @IsInstance(IndyRevocationInterval) + public nonRevoked?: IndyRevocationInterval + + @IsIn(['1.0', '2.0']) + @IsOptional() + public ver?: '1.0' | '2.0' +} diff --git a/packages/anoncreds/src/models/AnonCredsRequestedAttribute.ts b/packages/anoncreds/src/models/AnonCredsRequestedAttribute.ts new file mode 100644 index 0000000000..806f5f422b --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsRequestedAttribute.ts @@ -0,0 +1,39 @@ +import { Expose, Type } from 'class-transformer' +import { ArrayNotEmpty, IsArray, IsInstance, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator' + +import { AnonCredsRestriction, AnonCredsRestrictionTransformer } from './AnonCredsRestriction' +import { AnonCredsRevocationInterval } from './AnonCredsRevocationInterval' + +export class AnonCredsRequestedAttribute { + public constructor(options: AnonCredsRequestedAttribute) { + if (options) { + this.name = options.name + this.names = options.names + this.nonRevoked = options.nonRevoked ? new AnonCredsRevocationInterval(options.nonRevoked) : undefined + this.restrictions = options.restrictions?.map((r) => new AnonCredsRestriction(r)) + } + } + + @IsString() + @ValidateIf((o: AnonCredsRequestedAttribute) => o.names === undefined) + public name?: string + + @IsArray() + @IsString({ each: true }) + @ValidateIf((o: AnonCredsRequestedAttribute) => o.name === undefined) + @ArrayNotEmpty() + public names?: string[] + + @Expose({ name: 'non_revoked' }) + @ValidateNested() + @IsInstance(AnonCredsRevocationInterval) + @Type(() => AnonCredsRevocationInterval) + @IsOptional() + public nonRevoked?: AnonCredsRevocationInterval + + @ValidateNested({ each: true }) + @Type(() => AnonCredsRestriction) + @IsOptional() + @AnonCredsRestrictionTransformer() + public restrictions?: AnonCredsRestriction[] +} diff --git a/packages/anoncreds/src/models/AnonCredsRequestedPredicate.ts b/packages/anoncreds/src/models/AnonCredsRequestedPredicate.ts new file mode 100644 index 0000000000..5f9f99ebc0 --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsRequestedPredicate.ts @@ -0,0 +1,53 @@ +import { Expose, Type } from 'class-transformer' +import { IsArray, IsIn, IsInstance, IsInt, IsOptional, IsString, ValidateNested } from 'class-validator' + +import { AnonCredsPredicateType, anonCredsPredicateType } from '../models' + +import { AnonCredsRestriction, AnonCredsRestrictionTransformer } from './AnonCredsRestriction' +import { AnonCredsRevocationInterval } from './AnonCredsRevocationInterval' + +export interface AnonCredsRequestedPredicateOptions { + name: string + // Also allow string value of the enum as input, to make it easier to use in the API + predicateType: AnonCredsPredicateType + predicateValue: number + nonRevoked?: AnonCredsRevocationInterval + restrictions?: AnonCredsRestriction[] +} + +export class AnonCredsRequestedPredicate { + public constructor(options: AnonCredsRequestedPredicateOptions) { + if (options) { + this.name = options.name + this.nonRevoked = options.nonRevoked ? new AnonCredsRevocationInterval(options.nonRevoked) : undefined + this.restrictions = options.restrictions?.map((r) => new AnonCredsRestriction(r)) + this.predicateType = options.predicateType as AnonCredsPredicateType + this.predicateValue = options.predicateValue + } + } + + @IsString() + public name!: string + + @Expose({ name: 'p_type' }) + @IsIn(anonCredsPredicateType) + public predicateType!: AnonCredsPredicateType + + @Expose({ name: 'p_value' }) + @IsInt() + public predicateValue!: number + + @Expose({ name: 'non_revoked' }) + @ValidateNested() + @Type(() => AnonCredsRevocationInterval) + @IsOptional() + @IsInstance(AnonCredsRevocationInterval) + public nonRevoked?: AnonCredsRevocationInterval + + @ValidateNested({ each: true }) + @Type(() => AnonCredsRestriction) + @IsOptional() + @IsArray() + @AnonCredsRestrictionTransformer() + public restrictions?: AnonCredsRestriction[] +} diff --git a/packages/anoncreds/src/models/AnonCredsRestriction.ts b/packages/anoncreds/src/models/AnonCredsRestriction.ts new file mode 100644 index 0000000000..def1fc70a2 --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsRestriction.ts @@ -0,0 +1,139 @@ +import { Exclude, Expose, Transform, TransformationType } from 'class-transformer' +import { IsOptional, IsString } from 'class-validator' + +export class AnonCredsRestriction { + public constructor(options: AnonCredsRestriction) { + if (options) { + this.schemaId = options.schemaId + this.schemaIssuerDid = options.schemaIssuerDid + this.schemaIssuerId = options.schemaIssuerId + this.schemaName = options.schemaName + this.schemaVersion = options.schemaVersion + this.issuerDid = options.issuerDid + this.issuerId = options.issuerId + this.credentialDefinitionId = options.credentialDefinitionId + this.attributeMarkers = options.attributeMarkers + this.attributeValues = options.attributeValues + } + } + + @Expose({ name: 'schema_id' }) + @IsOptional() + @IsString() + public schemaId?: string + + @Expose({ name: 'schema_issuer_did' }) + @IsOptional() + @IsString() + public schemaIssuerDid?: string + + @Expose({ name: 'schema_issuer_id' }) + @IsOptional() + @IsString() + public schemaIssuerId?: string + + @Expose({ name: 'schema_name' }) + @IsOptional() + @IsString() + public schemaName?: string + + @Expose({ name: 'schema_version' }) + @IsOptional() + @IsString() + public schemaVersion?: string + + @Expose({ name: 'issuer_did' }) + @IsOptional() + @IsString() + public issuerDid?: string + + @Expose({ name: 'issuer_id' }) + @IsOptional() + @IsString() + public issuerId?: string + + @Expose({ name: 'cred_def_id' }) + @IsOptional() + @IsString() + public credentialDefinitionId?: string + + @Exclude() + public attributeMarkers: Record = {} + + @Exclude() + public attributeValues: Record = {} +} + +/** + * Decorator that transforms attribute values and attribute markers. + * + * It will transform between the following JSON structure: + * ```json + * { + * "attr::test_prop::value": "test_value" + * "attr::test_prop::marker": "1 + * } + * ``` + * + * And the following AnonCredsRestriction: + * ```json + * { + * "attributeValues": { + * "test_prop": "test_value" + * }, + * "attributeMarkers": { + * "test_prop": true + * } + * } + * ``` + * + * @example + * class Example { + * AttributeFilterTransformer() + * public restrictions!: AnonCredsRestriction[] + * } + */ +export function AnonCredsRestrictionTransformer() { + return Transform(({ value: restrictions, type }) => { + switch (type) { + case TransformationType.CLASS_TO_PLAIN: + if (restrictions && Array.isArray(restrictions)) { + for (const restriction of restrictions) { + const r = restriction as AnonCredsRestriction + + for (const [attributeName, attributeValue] of Object.entries(r.attributeValues)) { + restriction[`attr::${attributeName}::value`] = attributeValue + } + + for (const [attributeName] of Object.entries(r.attributeMarkers)) { + restriction[`attr::${attributeName}::marker`] = '1' + } + } + } + + return restrictions + + case TransformationType.PLAIN_TO_CLASS: + if (restrictions && Array.isArray(restrictions)) { + for (const restriction of restrictions) { + const r = restriction as AnonCredsRestriction + + for (const [attributeName, attributeValue] of Object.entries(r)) { + const match = new RegExp('^attr::([^:]+)::(value|marker)$').exec(attributeName) + + if (match && match[2] === 'marker' && attributeValue === '1') { + r.attributeMarkers[match[1]] = true + delete restriction[attributeName] + } else if (match && match[2] === 'value') { + r.attributeValues[match[1]] = attributeValue + delete restriction[attributeName] + } + } + } + } + return restrictions + default: + return restrictions + } + }) +} diff --git a/packages/anoncreds/src/models/AnonCredsRevocationInterval.ts b/packages/anoncreds/src/models/AnonCredsRevocationInterval.ts new file mode 100644 index 0000000000..0ae0160616 --- /dev/null +++ b/packages/anoncreds/src/models/AnonCredsRevocationInterval.ts @@ -0,0 +1,18 @@ +import { IsInt, IsOptional } from 'class-validator' + +export class AnonCredsRevocationInterval { + public constructor(options: AnonCredsRevocationInterval) { + if (options) { + this.from = options.from + this.to = options.to + } + } + + @IsInt() + @IsOptional() + public from?: number + + @IsInt() + @IsOptional() + public to?: number +} diff --git a/packages/anoncreds/src/models/__tests__/AnonCredsRestriction.test.ts b/packages/anoncreds/src/models/__tests__/AnonCredsRestriction.test.ts new file mode 100644 index 0000000000..23a9dd9317 --- /dev/null +++ b/packages/anoncreds/src/models/__tests__/AnonCredsRestriction.test.ts @@ -0,0 +1,80 @@ +import { JsonTransformer } from '@aries-framework/core' +import { Type } from 'class-transformer' +import { IsArray } from 'class-validator' + +import { AnonCredsRestriction, AnonCredsRestrictionTransformer } from '../AnonCredsRestriction' + +// We need to add the transformer class to the wrapper +class Wrapper { + public constructor(options: Wrapper) { + if (options) { + this.restrictions = options.restrictions + } + } + + @Type(() => AnonCredsRestriction) + @IsArray() + @AnonCredsRestrictionTransformer() + public restrictions!: AnonCredsRestriction[] +} + +describe('AnonCredsRestriction', () => { + test('parses attribute values and markers', () => { + const anonCredsRestrictions = JsonTransformer.fromJSON( + { + restrictions: [ + { + 'attr::test_prop::value': 'test_value', + 'attr::test_prop2::value': 'test_value2', + 'attr::test_prop::marker': '1', + 'attr::test_prop2::marker': '1', + }, + ], + }, + Wrapper + ) + + expect(anonCredsRestrictions).toEqual({ + restrictions: [ + { + attributeValues: { + test_prop: 'test_value', + test_prop2: 'test_value2', + }, + attributeMarkers: { + test_prop: true, + test_prop2: true, + }, + }, + ], + }) + }) + + test('transforms attributeValues and attributeMarkers to json', () => { + const restrictions = new Wrapper({ + restrictions: [ + new AnonCredsRestriction({ + attributeMarkers: { + test_prop: true, + test_prop2: true, + }, + attributeValues: { + test_prop: 'test_value', + test_prop2: 'test_value2', + }, + }), + ], + }) + + expect(JsonTransformer.toJSON(restrictions)).toMatchObject({ + restrictions: [ + { + 'attr::test_prop::value': 'test_value', + // 'attr::test_prop2::value': 'test_value2', + // 'attr::test_prop::marker': '1', + // 'attr::test_prop2::marker': '1', + }, + ], + }) + }) +}) diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 5899d82f1f..7ec87b9ec7 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -1,4 +1,5 @@ -export type AnonCredsPredicateType = '>=' | '>' | '<=' | '<' +export const anonCredsPredicateType = ['>=', '>', '<=', '<'] as const +export type AnonCredsPredicateType = (typeof anonCredsPredicateType)[number] export interface AnonCredsProofRequestRestriction { schema_id?: string diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index d36a2beb2e..1e897174b7 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -37,7 +37,7 @@ export interface CreateProofOptions { tailsFilePath: string definition: AnonCredsRevocationRegistryDefinition revocationStatusLists: { - [timestamp: string]: AnonCredsRevocationStatusList + [timestamp: number]: AnonCredsRevocationStatusList } } } diff --git a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts index 85593764af..1bdd959f15 100644 --- a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts @@ -15,7 +15,7 @@ export interface VerifyProofOptions { credentialDefinitions: { [credentialDefinitionId: string]: AnonCredsCredentialDefinition } - revocationStates: { + revocationRegistries: { [revocationRegistryDefinitionId: string]: { definition: AnonCredsRevocationRegistryDefinition // NOTE: the verifier only needs the accumulator, not the whole state of the revocation registry diff --git a/packages/anoncreds/src/formats/util/__tests__/areRequestsEqual.test.ts b/packages/anoncreds/src/utils/__tests__/areRequestsEqual.test.ts similarity index 99% rename from packages/anoncreds/src/formats/util/__tests__/areRequestsEqual.test.ts rename to packages/anoncreds/src/utils/__tests__/areRequestsEqual.test.ts index f8663f6779..51f9c3317e 100644 --- a/packages/anoncreds/src/formats/util/__tests__/areRequestsEqual.test.ts +++ b/packages/anoncreds/src/utils/__tests__/areRequestsEqual.test.ts @@ -1,4 +1,4 @@ -import type { AnonCredsProofRequest } from '../../../models' +import type { AnonCredsProofRequest } from '../../models' import { areAnonCredsProofRequestsEqual } from '../areRequestsEqual' diff --git a/packages/anoncreds/src/utils/__tests__/credential.test.ts b/packages/anoncreds/src/utils/__tests__/credential.test.ts index 0b81afe881..f75598bb3c 100644 --- a/packages/anoncreds/src/utils/__tests__/credential.test.ts +++ b/packages/anoncreds/src/utils/__tests__/credential.test.ts @@ -1,6 +1,10 @@ import { CredentialPreviewAttribute } from '@aries-framework/core' -import { assertCredentialValuesMatch, checkValidEncoding, convertAttributesToCredentialValues } from '../credential' +import { + assertCredentialValuesMatch, + checkValidCredentialValueEncoding, + convertAttributesToCredentialValues, +} from '../credential' /** * Sample test cases for encoding/decoding of verifiable credential claims - Aries RFCs 0036 and 0037 @@ -219,7 +223,7 @@ describe('Utils | Credentials', () => { ) test.each(testEntries)('returns true for valid encoding %s', (_, raw, encoded) => { - expect(checkValidEncoding(raw, encoded)).toEqual(true) + expect(checkValidCredentialValueEncoding(raw, encoded)).toEqual(true) }) }) }) diff --git a/packages/anoncreds/src/formats/util/__tests__/hasDuplicateGroupNames.test.ts b/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts similarity index 96% rename from packages/anoncreds/src/formats/util/__tests__/hasDuplicateGroupNames.test.ts rename to packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts index 88872091ba..4e7bab2ddd 100644 --- a/packages/anoncreds/src/formats/util/__tests__/hasDuplicateGroupNames.test.ts +++ b/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts @@ -1,4 +1,4 @@ -import type { AnonCredsProofRequest } from '../../../models' +import type { AnonCredsProofRequest } from '../../models' import { hasDuplicateGroupsNamesInProofRequest } from '../hasDuplicateGroupNames' diff --git a/packages/anoncreds/src/utils/__tests__/revocationInterval.test.ts b/packages/anoncreds/src/utils/__tests__/revocationInterval.test.ts new file mode 100644 index 0000000000..c95e8c70f6 --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/revocationInterval.test.ts @@ -0,0 +1,37 @@ +import { assertRevocationInterval } from '../../utils' + +describe('assertRevocationInterval', () => { + test("throws if no 'to' value is specified", () => { + expect(() => + assertRevocationInterval({ + from: 10, + }) + ).toThrow() + }) + + test("throws if a 'from' value is specified and it is different from 'to'", () => { + expect(() => + assertRevocationInterval({ + to: 5, + from: 10, + }) + ).toThrow() + }) + + test('does not throw if only to is provided', () => { + expect(() => + assertRevocationInterval({ + to: 5, + }) + ).not.toThrow() + }) + + test('does not throw if from and to are equal', () => { + expect(() => + assertRevocationInterval({ + to: 10, + from: 10, + }) + ).not.toThrow() + }) +}) diff --git a/packages/anoncreds/src/formats/util/__tests__/sortRequestedCredentialsMatches.test.ts b/packages/anoncreds/src/utils/__tests__/sortRequestedCredentialsMatches.test.ts similarity index 97% rename from packages/anoncreds/src/formats/util/__tests__/sortRequestedCredentialsMatches.test.ts rename to packages/anoncreds/src/utils/__tests__/sortRequestedCredentialsMatches.test.ts index 3cd296b138..0bd658a646 100644 --- a/packages/anoncreds/src/formats/util/__tests__/sortRequestedCredentialsMatches.test.ts +++ b/packages/anoncreds/src/utils/__tests__/sortRequestedCredentialsMatches.test.ts @@ -1,4 +1,4 @@ -import type { AnonCredsCredentialInfo, AnonCredsRequestedAttributeMatch } from '../../../models' +import type { AnonCredsCredentialInfo, AnonCredsRequestedAttributeMatch } from '../../models' import { sortRequestedCredentialsMatches } from '../sortRequestedCredentialsMatches' diff --git a/packages/anoncreds/src/formats/util/areRequestsEqual.ts b/packages/anoncreds/src/utils/areRequestsEqual.ts similarity index 98% rename from packages/anoncreds/src/formats/util/areRequestsEqual.ts rename to packages/anoncreds/src/utils/areRequestsEqual.ts index 56bb5d6f5b..759312cf87 100644 --- a/packages/anoncreds/src/formats/util/areRequestsEqual.ts +++ b/packages/anoncreds/src/utils/areRequestsEqual.ts @@ -1,4 +1,4 @@ -import type { AnonCredsNonRevokedInterval, AnonCredsProofRequest, AnonCredsProofRequestRestriction } from '../../models' +import type { AnonCredsNonRevokedInterval, AnonCredsProofRequest, AnonCredsProofRequestRestriction } from '../models' // Copied from the core package so we don't have to export these silly utils. We should probably move these to a separate package. // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/packages/anoncreds/src/formats/util/createRequestFromPreview.ts b/packages/anoncreds/src/utils/createRequestFromPreview.ts similarity index 96% rename from packages/anoncreds/src/formats/util/createRequestFromPreview.ts rename to packages/anoncreds/src/utils/createRequestFromPreview.ts index a24b4196ec..d1738d000e 100644 --- a/packages/anoncreds/src/formats/util/createRequestFromPreview.ts +++ b/packages/anoncreds/src/utils/createRequestFromPreview.ts @@ -1,8 +1,8 @@ -import type { AnonCredsProofRequest } from '../../models' import type { AnonCredsPresentationPreviewAttribute, AnonCredsPresentationPreviewPredicate, -} from '../AnonCredsProofFormat' +} from '../formats/AnonCredsProofFormat' +import type { AnonCredsProofRequest } from '../models' import { utils } from '@aries-framework/core' diff --git a/packages/anoncreds/src/utils/credential.ts b/packages/anoncreds/src/utils/credential.ts index 6310270980..eee27cccab 100644 --- a/packages/anoncreds/src/utils/credential.ts +++ b/packages/anoncreds/src/utils/credential.ts @@ -1,7 +1,7 @@ import type { AnonCredsSchema, AnonCredsCredentialValues } from '../models' import type { CredentialPreviewAttributeOptions, LinkedAttachment } from '@aries-framework/core' -import { CredentialPreviewAttribute, AriesFrameworkError, Hasher, encodeAttachment } from '@aries-framework/core' +import { AriesFrameworkError, Hasher, encodeAttachment } from '@aries-framework/core' import BigNumber from 'bn.js' const isString = (value: unknown): value is string => typeof value === 'string' @@ -34,7 +34,7 @@ export function convertAttributesToCredentialValues( return { [attribute.name]: { raw: attribute.value, - encoded: encode(attribute.value), + encoded: encodeCredentialValue(attribute.value), }, ...credentialValues, } @@ -109,8 +109,8 @@ export function assertCredentialValuesMatch( * @see https://github.com/hyperledger/aries-framework-dotnet/blob/a18bef91e5b9e4a1892818df7408e2383c642dfa/src/Hyperledger.Aries/Utils/CredentialUtils.cs#L78-L89 * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0037-present-proof/README.md#verifying-claims-of-indy-based-verifiable-credentials */ -export function checkValidEncoding(raw: unknown, encoded: string) { - return encoded === encode(raw) +export function checkValidCredentialValueEncoding(raw: unknown, encoded: string) { + return encoded === encodeCredentialValue(raw) } /** @@ -123,7 +123,7 @@ export function checkValidEncoding(raw: unknown, encoded: string) { * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0037-present-proof/README.md#verifying-claims-of-indy-based-verifiable-credentials * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0036-issue-credential/README.md#encoding-claims-for-indy-based-verifiable-credentials */ -export function encode(value: unknown) { +export function encodeCredentialValue(value: unknown) { const isEmpty = (value: unknown) => isString(value) && value === '' // If bool return bool as number string @@ -153,7 +153,7 @@ export function encode(value: unknown) { return new BigNumber(Hasher.hash(Buffer.from(value as string), 'sha2-256')).toString() } -export function assertAttributesMatch(schema: AnonCredsSchema, attributes: CredentialPreviewAttribute[]) { +export function assertAttributesMatch(schema: AnonCredsSchema, attributes: CredentialPreviewAttributeOptions[]) { const schemaAttributes = schema.attrNames const credAttributes = attributes.map((a) => a.name) @@ -178,7 +178,7 @@ export function assertAttributesMatch(schema: AnonCredsSchema, attributes: Crede * */ export function createAndLinkAttachmentsToPreview( attachments: LinkedAttachment[], - previewAttributes: CredentialPreviewAttribute[] + previewAttributes: CredentialPreviewAttributeOptions[] ) { const credentialPreviewAttributeNames = previewAttributes.map((attribute) => attribute.name) const newPreviewAttributes = [...previewAttributes] @@ -187,12 +187,11 @@ export function createAndLinkAttachmentsToPreview( if (credentialPreviewAttributeNames.includes(linkedAttachment.attributeName)) { throw new AriesFrameworkError(`linkedAttachment ${linkedAttachment.attributeName} already exists in the preview`) } else { - const credentialPreviewAttribute = new CredentialPreviewAttribute({ + newPreviewAttributes.push({ name: linkedAttachment.attributeName, mimeType: linkedAttachment.attachment.mimeType, value: encodeAttachment(linkedAttachment.attachment), }) - newPreviewAttributes.push(credentialPreviewAttribute) } }) diff --git a/packages/anoncreds/src/formats/util/hasDuplicateGroupNames.ts b/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts similarity index 94% rename from packages/anoncreds/src/formats/util/hasDuplicateGroupNames.ts rename to packages/anoncreds/src/utils/hasDuplicateGroupNames.ts index 64e3f26884..f4915fe6fc 100644 --- a/packages/anoncreds/src/formats/util/hasDuplicateGroupNames.ts +++ b/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts @@ -1,4 +1,4 @@ -import type { AnonCredsProofRequest } from '../../models' +import type { AnonCredsProofRequest } from '../models' function attributeNamesToArray(proofRequest: AnonCredsProofRequest) { // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array diff --git a/packages/anoncreds/src/formats/util/index.ts b/packages/anoncreds/src/utils/index.ts similarity index 57% rename from packages/anoncreds/src/formats/util/index.ts rename to packages/anoncreds/src/utils/index.ts index ff85ceabdd..a140e13cfb 100644 --- a/packages/anoncreds/src/formats/util/index.ts +++ b/packages/anoncreds/src/utils/index.ts @@ -2,3 +2,7 @@ export { createRequestFromPreview } from './createRequestFromPreview' export { sortRequestedCredentialsMatches } from './sortRequestedCredentialsMatches' export { hasDuplicateGroupsNamesInProofRequest } from './hasDuplicateGroupNames' export { areAnonCredsProofRequestsEqual } from './areRequestsEqual' +export { downloadTailsFile } from './tails' +export { assertRevocationInterval } from './revocationInterval' +export { encodeCredentialValue, checkValidCredentialValueEncoding } from './credential' +export { IsMap } from './isMap' diff --git a/packages/anoncreds/src/utils/isMap.ts b/packages/anoncreds/src/utils/isMap.ts new file mode 100644 index 0000000000..1ee81fe4a4 --- /dev/null +++ b/packages/anoncreds/src/utils/isMap.ts @@ -0,0 +1,19 @@ +import type { ValidationOptions } from 'class-validator' + +import { ValidateBy, buildMessage } from 'class-validator' + +/** + * Checks if a given value is a Map + */ +export function IsMap(validationOptions?: ValidationOptions): PropertyDecorator { + return ValidateBy( + { + name: 'isMap', + validator: { + validate: (value: unknown): boolean => value instanceof Map, + defaultMessage: buildMessage((eachPrefix) => eachPrefix + '$property must be a Map', validationOptions), + }, + }, + validationOptions + ) +} diff --git a/packages/anoncreds/src/utils/revocationInterval.ts b/packages/anoncreds/src/utils/revocationInterval.ts new file mode 100644 index 0000000000..caf40b93c1 --- /dev/null +++ b/packages/anoncreds/src/utils/revocationInterval.ts @@ -0,0 +1,17 @@ +import type { AnonCredsNonRevokedInterval } from '../models' + +import { AriesFrameworkError } from '@aries-framework/core' + +// TODO: Add Test +// Check revocation interval in accordance with https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0441-present-proof-best-practices/README.md#semantics-of-non-revocation-interval-endpoints +export function assertRevocationInterval(nonRevokedInterval: AnonCredsNonRevokedInterval) { + if (!nonRevokedInterval.to) { + throw new AriesFrameworkError(`Presentation requests proof of non-revocation with no 'to' value specified`) + } + + if ((nonRevokedInterval.from || nonRevokedInterval.from === 0) && nonRevokedInterval.to !== nonRevokedInterval.from) { + throw new AriesFrameworkError( + `Presentation requests proof of non-revocation with an interval from: '${nonRevokedInterval.from}' that does not match the interval to: '${nonRevokedInterval.to}', as specified in Aries RFC 0441` + ) + } +} diff --git a/packages/anoncreds/src/formats/util/sortRequestedCredentialsMatches.ts b/packages/anoncreds/src/utils/sortRequestedCredentialsMatches.ts similarity index 97% rename from packages/anoncreds/src/formats/util/sortRequestedCredentialsMatches.ts rename to packages/anoncreds/src/utils/sortRequestedCredentialsMatches.ts index f7568cfc3d..1d190c7e31 100644 --- a/packages/anoncreds/src/formats/util/sortRequestedCredentialsMatches.ts +++ b/packages/anoncreds/src/utils/sortRequestedCredentialsMatches.ts @@ -1,4 +1,4 @@ -import type { AnonCredsRequestedAttributeMatch, AnonCredsRequestedPredicateMatch } from '../../models' +import type { AnonCredsRequestedAttributeMatch, AnonCredsRequestedPredicateMatch } from '../models' /** * Sort requested attributes and predicates by `revoked` status. The order is: diff --git a/packages/anoncreds/src/utils/tails.ts b/packages/anoncreds/src/utils/tails.ts new file mode 100644 index 0000000000..9ae29aa8e4 --- /dev/null +++ b/packages/anoncreds/src/utils/tails.ts @@ -0,0 +1,57 @@ +import type { AgentContext, FileSystem } from '@aries-framework/core' + +import { TypedArrayEncoder, InjectionSymbols } from '@aries-framework/core' + +const getTailsFilePath = (basePath: string, tailsHash: string) => `${basePath}/afj/anoncreds/tails/${tailsHash}` + +export function tailsFileExists(agentContext: AgentContext, tailsHash: string): Promise { + const fileSystem = agentContext.dependencyManager.resolve(InjectionSymbols.FileSystem) + const tailsFilePath = getTailsFilePath(fileSystem.basePath, tailsHash) + + return fileSystem.exists(tailsFilePath) +} + +export async function downloadTailsFile( + agentContext: AgentContext, + tailsLocation: string, + tailsHashBase58: string +): Promise<{ + tailsFilePath: string +}> { + const fileSystem = agentContext.dependencyManager.resolve(InjectionSymbols.FileSystem) + + try { + agentContext.config.logger.debug( + `Checking to see if tails file for URL ${tailsLocation} has been stored in the FileSystem` + ) + + // hash is used as file identifier + const tailsExists = await tailsFileExists(agentContext, tailsHashBase58) + const tailsFilePath = getTailsFilePath(fileSystem.basePath, tailsHashBase58) + agentContext.config.logger.debug( + `Tails file for ${tailsLocation} ${tailsExists ? 'is stored' : 'is not stored'} at ${tailsFilePath}` + ) + + if (!tailsExists) { + agentContext.config.logger.debug(`Retrieving tails file from URL ${tailsLocation}`) + + // download file and verify hash + await fileSystem.downloadToFile(tailsLocation, tailsFilePath, { + verifyHash: { + algorithm: 'sha256', + hash: TypedArrayEncoder.fromBase58(tailsHashBase58), + }, + }) + agentContext.config.logger.debug(`Saved tails file to FileSystem at path ${tailsFilePath}`) + } + + return { + tailsFilePath, + } + } catch (error) { + agentContext.config.logger.error(`Error while retrieving tails file from URL ${tailsLocation}`, { + error, + }) + throw error + } +} diff --git a/packages/core/package.json b/packages/core/package.json index 8a54af7192..06b6aad06c 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -59,6 +59,7 @@ "@types/object-inspect": "^1.8.0", "@types/uuid": "^8.3.0", "@types/varint": "^6.0.0", + "nock": "^13.3.0", "node-fetch": "^2.0", "rimraf": "^4.0.7", "tslog": "^3.2.0", diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index ee9c82dfa0..2f3142a00a 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -28,7 +28,7 @@ export type { WalletExportImportConfig, } from './types' export { DidCommMimeType, KeyDerivationMethod } from './types' -export type { FileSystem } from './storage/FileSystem' +export type { FileSystem, DownloadToFileOptions } from './storage/FileSystem' export * from './storage/BaseRecord' export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository' export { Repository } from './storage/Repository' diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index 2d79961ebb..9e438c6d1c 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -2,7 +2,7 @@ import type { CredentialFormat, CredentialFormatPayload } from './CredentialForm import type { CredentialFormatService } from './CredentialFormatService' import type { Attachment } from '../../../decorators/attachment/Attachment' import type { CredentialFormatSpec } from '../models/CredentialFormatSpec' -import type { CredentialPreviewAttribute } from '../models/CredentialPreviewAttribute' +import type { CredentialPreviewAttributeOptions } from '../models/CredentialPreviewAttribute' import type { CredentialExchangeRecord } from '../repository/CredentialExchangeRecord' /** @@ -72,7 +72,7 @@ export interface CredentialFormatAcceptProposalOptions { @@ -90,7 +90,7 @@ export interface CredentialFormatAcceptOfferOptions } export interface CredentialFormatCreateOfferReturn extends CredentialFormatCreateReturn { - previewAttributes?: CredentialPreviewAttribute[] + previewAttributes?: CredentialPreviewAttributeOptions[] } export interface CredentialFormatCreateRequestOptions { diff --git a/packages/core/src/modules/credentials/models/CredentialPreviewAttribute.ts b/packages/core/src/modules/credentials/models/CredentialPreviewAttribute.ts index 89c3397b09..0f341785c4 100644 --- a/packages/core/src/modules/credentials/models/CredentialPreviewAttribute.ts +++ b/packages/core/src/modules/credentials/models/CredentialPreviewAttribute.ts @@ -35,5 +35,5 @@ export class CredentialPreviewAttribute { } export interface CredentialPreviewOptions { - attributes: CredentialPreviewAttribute[] + attributes: CredentialPreviewAttributeOptions[] } diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts index 59338c7835..6ee07a7588 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts @@ -177,7 +177,7 @@ export class V1CredentialProtocol associatedRecordId: credentialRecord.id, }) - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = credentialProposal?.attributes await credentialRepository.save(agentContext, credentialRecord) this.emitStateChangedEvent(agentContext, credentialRecord, null) @@ -336,7 +336,7 @@ export class V1CredentialProtocol message.setThread({ threadId: credentialRecord.threadId }) - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = message.credentialPreview.attributes credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential await this.updateState(agentContext, credentialRecord, CredentialState.OfferSent) @@ -393,7 +393,7 @@ export class V1CredentialProtocol }) message.setThread({ threadId: credentialRecord.threadId }) - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = message.credentialPreview.attributes credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential await this.updateState(agentContext, credentialRecord, CredentialState.OfferSent) @@ -472,7 +472,7 @@ export class V1CredentialProtocol role: DidCommMessageRole.Sender, }) - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = message.credentialPreview.attributes await credentialRepository.save(agentContext, credentialRecord) this.emitStateChangedEvent(agentContext, credentialRecord, null) @@ -707,7 +707,7 @@ export class V1CredentialProtocol }) // Update record - credentialRecord.credentialAttributes = previewAttributes + credentialRecord.credentialAttributes = message.credentialPreview?.attributes credentialRecord.linkedAttachments = linkedAttachments?.map((attachment) => attachment.attachment) credentialRecord.autoAcceptCredential = autoAcceptCredential ?? credentialRecord.autoAcceptCredential await this.updateState(agentContext, credentialRecord, CredentialState.ProposalSent) diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts b/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts index 9fe8aa5fc3..da44d37618 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts +++ b/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts @@ -17,7 +17,7 @@ import { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAtt export class V1CredentialPreview { public constructor(options: CredentialPreviewOptions) { if (options) { - this.attributes = options.attributes + this.attributes = options.attributes.map((a) => new CredentialPreviewAttribute(a)) } } diff --git a/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialPreview.ts b/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialPreview.ts index d566faa1a0..ea78448593 100644 --- a/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialPreview.ts +++ b/packages/core/src/modules/credentials/protocol/v2/messages/V2CredentialPreview.ts @@ -17,7 +17,7 @@ import { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAtt export class V2CredentialPreview { public constructor(options: CredentialPreviewOptions) { if (options) { - this.attributes = options.attributes + this.attributes = options.attributes.map((a) => new CredentialPreviewAttribute(a)) } } diff --git a/packages/core/src/storage/FileSystem.ts b/packages/core/src/storage/FileSystem.ts index b724e68158..c5996e78b2 100644 --- a/packages/core/src/storage/FileSystem.ts +++ b/packages/core/src/storage/FileSystem.ts @@ -1,3 +1,9 @@ +import type { Buffer } from '../utils/buffer' + +export interface DownloadToFileOptions { + verifyHash?: { algorithm: 'sha256'; hash: Buffer } +} + export interface FileSystem { readonly basePath: string @@ -5,5 +11,5 @@ export interface FileSystem { createDirectory(path: string): Promise write(path: string, data: string): Promise read(path: string): Promise - downloadToFile(url: string, path: string): Promise + downloadToFile(url: string, path: string, options?: DownloadToFileOptions): Promise } diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts index 42ae286038..e4e4cb1d2d 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts @@ -1,6 +1,6 @@ import type { AnonCredsVerifierService, VerifyProofOptions } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' -import type { CredentialDefs, Schemas, RevocRegDefs, RevRegs, IndyProofRequest } from 'indy-sdk' +import type { CredentialDefs, Schemas, RevocRegDefs, RevRegs, IndyProofRequest, IndyProof } from 'indy-sdk' import { inject, injectable } from '@aries-framework/core' @@ -54,8 +54,8 @@ export class IndySdkVerifierService implements AnonCredsVerifierService { const indyRevocationDefinitions: RevocRegDefs = {} const indyRevocationRegistries: RevRegs = {} - for (const revocationRegistryDefinitionId in options.revocationStates) { - const { definition, revocationStatusLists } = options.revocationStates[revocationRegistryDefinitionId] + for (const revocationRegistryDefinitionId in options.revocationRegistries) { + const { definition, revocationStatusLists } = options.revocationRegistries[revocationRegistryDefinitionId] indyRevocationDefinitions[revocationRegistryDefinitionId] = indySdkRevocationRegistryDefinitionFromAnonCreds( revocationRegistryDefinitionId, definition @@ -75,7 +75,7 @@ export class IndySdkVerifierService implements AnonCredsVerifierService { return await this.indySdk.verifierVerifyProof( options.proofRequest as IndyProofRequest, - options.proof, + options.proof as IndyProof, indySchemas, indyCredentialDefinitions, indyRevocationDefinitions, diff --git a/packages/node/src/NodeFileSystem.ts b/packages/node/src/NodeFileSystem.ts index 240440d64c..a5caf0d070 100644 --- a/packages/node/src/NodeFileSystem.ts +++ b/packages/node/src/NodeFileSystem.ts @@ -1,5 +1,7 @@ -import type { FileSystem } from '@aries-framework/core' +import type { DownloadToFileOptions, FileSystem } from '@aries-framework/core' +import { AriesFrameworkError, TypedArrayEncoder } from '@aries-framework/core' +import { createHash } from 'crypto' import fs, { promises } from 'fs' import http from 'http' import https from 'https' @@ -44,13 +46,14 @@ export class NodeFileSystem implements FileSystem { return readFile(path, { encoding: 'utf-8' }) } - public async downloadToFile(url: string, path: string) { + public async downloadToFile(url: string, path: string, options: DownloadToFileOptions) { const httpMethod = url.startsWith('https') ? https : http // Make sure parent directories exist await promises.mkdir(dirname(path), { recursive: true }) const file = fs.createWriteStream(path) + const hash = options.verifyHash ? createHash('sha256') : undefined return new Promise((resolve, reject) => { httpMethod @@ -60,9 +63,26 @@ export class NodeFileSystem implements FileSystem { reject(`Unable to download file from url: ${url}. Response status was ${response.statusCode}`) } + hash && response.pipe(hash) response.pipe(file) - file.on('finish', () => { + file.on('finish', async () => { file.close() + + if (hash && options.verifyHash?.hash) { + hash.end() + const digest = hash.digest() + if (digest.compare(options.verifyHash.hash) !== 0) { + await fs.promises.unlink(path) + + reject( + new AriesFrameworkError( + `Hash of downloaded file does not match expected hash. Expected: ${ + options.verifyHash.hash + }, Actual: ${TypedArrayEncoder.toUtf8String(digest)})}` + ) + ) + } + } resolve() }) }) diff --git a/packages/node/tests/NodeFileSystem.test.ts b/packages/node/tests/NodeFileSystem.test.ts index e242b43cdd..f031ee32e5 100644 --- a/packages/node/tests/NodeFileSystem.test.ts +++ b/packages/node/tests/NodeFileSystem.test.ts @@ -1,13 +1,42 @@ +import { TypedArrayEncoder } from '@aries-framework/core' +import nock, { cleanAll, enableNetConnect } from 'nock' +import path from 'path' + import { NodeFileSystem } from '../src/NodeFileSystem' describe('@aries-framework/file-system-node', () => { describe('NodeFileSystem', () => { const fileSystem = new NodeFileSystem() + afterAll(() => { + cleanAll() + enableNetConnect() + }) + describe('exists()', () => { it('should return false if the pash does not exist', () => { return expect(fileSystem.exists('some-random-path')).resolves.toBe(false) }) }) + + describe('downloadToFile()', () => { + test('should verify the hash', async () => { + // Mock tails file + nock('https://tails.prod.absa.africa') + .get('/api/public/tails/4B1NxYuGxwYMe5BAyP9NXkUmbEkDATo4oGZCgjXQ3y1p') + .replyWithFile(200, path.join(__dirname, '__fixtures__/tailsFile')) + + await fileSystem.downloadToFile( + 'https://tails.prod.absa.africa/api/public/tails/4B1NxYuGxwYMe5BAyP9NXkUmbEkDATo4oGZCgjXQ3y1p', + `${fileSystem.basePath}/afj/tails/4B1NxYuGxwYMe5BAyP9NXkUmbEkDATo4oGZCgjXQ3y1p`, + { + verifyHash: { + algorithm: 'sha256', + hash: TypedArrayEncoder.fromBase58('4B1NxYuGxwYMe5BAyP9NXkUmbEkDATo4oGZCgjXQ3y1p'), + }, + } + ) + }) + }) }) }) diff --git a/packages/node/tests/__fixtures__/tailsFile b/packages/node/tests/__fixtures__/tailsFile new file mode 100644 index 0000000000000000000000000000000000000000..73f04718605544e6cbb4c49181306e356efa12c3 GIT binary patch literal 65666 zcmV(tKuDUepj0~g+<6Wf7WvDI z^GQSBmK)5pUB|np892s;rgJQZ6Ib>n#4-nBHv}~fu* ztq>ryW<^tcj8!vJk#Bs?@^WNDvj{c>%Z_{iG#C!m4F@&y#y5eIZZUPH$bDrj0RU>Z zcTJEI)|@A-hplGjRTmPqdB*q6Jy}8x%#WXW+So}EzK3^(I%>W8r;;Zd$`d>KyrUS4 zPgr?ib3eW=Dq&a08HG{)%@Vm@;je4m4HE$K4g_UgwXXyd;%9VEheH3_`C#s8FIGI( z7ZahjFBTT#z$TD7VNz5^dE4A23ln+y%<#MFa(ODE=uVWfngTVjXEn}7%I6!c2f}>J zY&%aQsitsZ@WA;Fwnr~-uMZYo24D8)HRN`F*fl|d8U7B^K6ebYBtq|$NE;9pIYDfI z9(xbJH)B}3EFzm>#vd#k9X0$lisloB4toZ%AFLNtc4cl*z0v=Kh#nz&Z8`=B95I5e zP|lZ3cgoAXJwXi#Tp>q@kb!{MMGUCSI3KB7yuHq_i^Vr?c^9h^TzH!%3&8=_iWGdl z<0K{i9bQ8}=m-K%l=&!>4@3K_x5-ccjrdI@?_zLHVw>5Al>#A&kH}|*rNY0k3l%llV5&+moLp?JrZ-=1mWC&dW<7`*@@~v3xq^vV7d)#X~mQ` zN%OFtw---axd$>Y@2aW69*d4m2&G!w;8li_dDo;3N#ppO9tZ?t3mSzDf*u$6Jxv!>->%tIB>D;hn)Q3M^; zG0_VfM}N`@q0+0y^?01Ku3{g4tMV?bV2#w-y#V5!s1_>jF=oSd>a8Vqt-8i+{}=7# zH@7k30h}{o+YG4*b9}bm4O#4oT2a~Pw(Y_l>|7bx=>L#6ek*B7mdYHbsH1n24g$7$Hzt zv<*Pw3?v^dmwPqmq|&<~^V9ntk{)eM80U7`v4_%#;|aBX-5QUhp^^T^56P#RQiFFV zU{oPBX-NM*DdQ955;|(T=?mioBk6RJg@T%_@xOmkitM|!?RPUl&lm-4QKBh_b^)9Q zr}I(OGHdKVx%{`YS$s4K0xlNo6ScY+@9m@f3IoOXzm0mzu|wdQsQ?6fw3FVqkJPRB zaus>WH*bETwqnbxV|?XdX;{D*-0TwOj(@dM@duDdJ3u)|Clt!ZtxA)?3}&1(`ovQ3X#S%sU|9 z8_m?zj1DOq$$$nM7~tv~xJ)@X;z9a%?@ZiR_+!iq@WYc!ITZ!ThO1`u8&wRaO>~Y& z>YY+!Muzoi2e81ly%za$MG|cgwgrMjk*jmZouf_&pb-*K zfVU!0z+>W1x+1mWQJCc`9jL~k&EzkN7%Ps^;~&S6Fbp*21wnx~M3R%^5dJXojk92< zfCX?n;3>-aDI0E)XBxXS+|enJ`{&KO)MEUtKH|}b>gZTTon4}9kq{rueZR+&XAUEn z%cj{F@VE5EShP3+_&Jl+WL~Wi9U9T%`z^{b!4$uwc`Gs4TvdvM%@0$>+z_qJNmX+m zP8*qD%P{{EN5w)z&X@M<*gg^Cd2iRIO@ecsh&l-off$CBj97kSicTfEW0WN2e|2bq zI}9PU1KBT$z0r)lA`CIgKBky3EePx{jc)O7DopgZ;`Hh@0FyUkhx#Dcz#fI3Jmd;x zxkBWCZcMWY@(*9RNWUkf@!V4oAFVOSvJ5}HO8{7|D!kF33vx(*C9gRxpgp0dXQslb zH8o{M{0}R-Cm|Ab?BiU9^WC0YwRjEg^(u>j9amd!;^{u)Hc$SMcuyO@$0MLp?j?-^$rTYf7Dx**rtnNJ|t^QE&AN~`5eOOY$m0C@8~IuAQq zW77+&nRqgJ03NLQk!q-A7_strVF!)iQv*4mLF}$QRx1ehX_)?#9iGN1 zod|8LFI)?Z{{LW5Wzl+L3J9eWdAAcrc1=lt{@lZ<1jMQQhOBs++Ys1q9q5T8hrJ5j8+o%(Ja`bItOs=SM@-@|u3!*MtMvL+VTS zJWj7c))@Mn^yQDsD%-N8t9r@lWkvKEFznNhi7ps3U-61DKoVIgq=wMoznv>{GpVXq zsETcE^f+>u8uLyX8-f@X3kSf%GB3z|Aq|B7iEk(=MK1Arxa71XG-!S%+LY17 zt02u>1cr1ugM-ae?A3_}lu$$Da}h$h__|>YDD)>PfEih7t9Na3+0!m6i8F*!u%li| ziI2x^*kx{m;zq@!PX{F)jkiP#S~Au#ygUgoATe8=lNILgF1cLrNRDai@J8M2wa~axL`SAP!Ilm zck5`T657HA8L`iwkXhP(V&lwXtGBE&eqkGl9}>pGp%7u+9qs|ETpZAA3>AQ)rZ8aVQoK zqmfnjS~4P!Z5AiSvmr6W*$$kbbQ-FA%Uz$jr_b2$_2sjhaIwtF79!U8e0?DFYUj0> zjWL$8(C69lxxZZTUPyJ}nQ-M?+ZCA#;QIbdJ|#_&P-ib*$8B$i_#S_D+cVLI;AhWK zs0SyfNk0U0B*aTxAtpC0yzXMM8d-0O`Ft2bX|eKg2oy+w%H0dC7;G^rl#G^$f_T4J z^%F_p+*BF$CX%);?h<=xh0P0Hz4K)AZZ20O0~)e)$|M_DIJAE1HFaumLxY68YiSDhA#xD??*= z1P~V%dP!(6hwj_NmmM3$W*g|*Jl5LLS5-EAaaY<@S<~%k(Oc=ZCW0|VSUCuch71mo zSKf>b_kgBN1_rmDt1xe+wZ25gT(%4XP2>(KgA-+Af@l0AsiC7&Ek5?}Ok%QK+BcGa zu%r0FS3w(z2_GY25i^c8-W?mmTbc?GdvpL8>I4$`H=h|{AdrYbJ{=;rCD<2=t!rXR zq2x_^=WtDur>KizT*IYN``_st_z(|k!+8Y^5~&Wd8CeZc=qbh8(eFrtT|sIk0?;%s zCKaPIWA80f*R=<5*_(7>dKI2WLXz@{aZUV)3OQzDY$DF?ns=(bQ)u|9UmsohxXb@U z>qBIQjy$aq^%tx#FB&{c`?TAoTD93Sd5rIvNGgvU&G#j&)(-MnaF|mC zRxFMjj^RjbFmoWG{y2Te0Th|fz|Tqkz!)`P8f4qZ-_Yad`S6G{@IwgmKd)Lja&k|R z6o+#aE*|dd#?B}zol2Wv+}1sW4S0j~XdY}LkXICWA8sWeYazp*)mH1NjxAf!8;`2^ zD>t!jh&JbQX}X9M=Xag3)*%!pZURo_2C-P$Ix)@yp#2KOK@?!Vh_?DgU8mAZ%^z+O zz<+H!HEWb!u=HfG;BTw|WKP{5JHE(Uj7bY09szppkm#v{lqEMCA9mK;bp6sJbMjxQ z(O!=Plr{D}=mp=fJmQR0Owg6_V+}9k4u?TH6vy@6{oDq33v({Ym{am4a@xu!y%eOwT zgCp4|c+gtE_IPaeh6r>OzM>*xlGX7cq)s92rHzRXCYtd>0knOU&6t)qJ{AJ-f2C@5 zQ{T3`rR@pM3;ShZ?r|iCdgZ0x0PjxD0}a95zabgNcXM$3wt9(%SigF5Cc^1=bf-{f zC_S0DoFEl;hL$hbWp32uAR94FPtc?j2Dn2uF)-WXb@m2fNFd-G*NPoS@|k)XNQd!c z>-HnJG|%IJhZVCVNXqIRhYlzgZ0$vWih#_;XdS^-4*)tDU?DPs8H(3-$zrSb=?SX3 zPETT=>DdatdAvSCx-hC!!7i{+GCZp|-$R|J@5mtP-*M(B^{ha95|-o zhc`D7K@0fZ4neu8t_aDT9_oFaB9l<#j1INP0G3V9*m(j3y>a&D=VP$j=n|Ufk;&WB zTuWfVAd|MEmv36KE&l*=0U=k+n|Is_TM^{B$2g^!97&|1QIZqa%(vYE8Qq!k2<1S< z*A={Ff*cq~*-{?!rZl~aTH*+exkA@Bkh!RtWh@G}3{*l;x*RV5KSr*l@9+^m^9K!M zL_uRr)BA}(vdeV-=~28K_8DG*AAH)A*_YGR$I>%f<*3$(4DL4zXg z5EB`J(0n@AeiAWF&?ltLoO}i@yu287-^|$m*~O4vtR2hY3F7DStvfzB-W2)=(X=Pp zBe-7#s1&i`W23k-AQecG=!D;8Aa0QzNwd&wph>I;bz%^+G4m&^(9fZo=G13~p?uy!SPr7WWsrX=?mi6oNMIh*DZe%;ISjEeIB zmZ{$F>^~kUl+qVAZA~OEn;c7Z|3}n1zk?YK6!dLV7TZ;e>hx`Nr{1EQ3G&dC76pQW z4S+L8dcv19&WF$Qp(`{A(%04)o!*qK>>Hylu>JixsmzaN0FP=-(^gq25zPAv`Yd=b12ChBY_ zb|2P$ncose2b;$N(iQeT9@62;p#}d{B6Y|ei&`vWogol%1R`nnH`H!#tV$g~SPVwr zv^~DZ_vrO5rS6T!3=ct6I3n@kTmTWsS(Qp`l7rPu`5*LhH)NrEawBoK8y{v=KEBYp zwf9!zxRWh5)bZb!x0p}KBQ$uWyHEpsCl5Oq-)o!{VT_`>sYl&!R7uaowQM{lNZj8_*)%Uj&*wYWJH% z%;e)sKH}60=SaPjlaksO9}Ar~%1LJcez-$1hR&KnYe@3REy)%JNnjNJ4ib?&2MVN} z+byi`?h{fE#Yfosu|V~a{2_G_G6tCHa$AW~mk`=A2HtTrR#8|&$zyUD*`2m>eC%N;v8%>P)aH@Zk1*h=nKeE7K0;ZCDhZs zH!|F#nZZo#*TaJ+NyWMd^g1GRZ3$>G9oRO5vu@&AsQ>krF?Cyys9kpye&ky0ZZ9M8 zV;byGo9nRk7Mer$@DkqX?#H(A|4Mx}%rQTJ2_-`CrwsM|fl;tzdpAEBDu@!t9~*29 zw>Q&eE$^8Wtw=7~uoO!>;pJ!|^El>YgJyl)Aac3qL%Op}oLCGq{+kMe#1M>UW}H1x zt_Vz3R|QaPtdPFny8O7X(e&8Ub$gp092D?PMrYZ@Q#7Iz>z({tk0DA;A}L?FL8)FG zSVAE+fF!TDvT7nfp%?#GEI3qHyVH23=bA#iXHH?-8^o{X9)%0NJS^fC zXRjSps9}pkMxoKa@N<3~yC6Jb^R@fG%4qaYk^98+Wt^FEcX$fW)%K7Zv}$12iVio% zEW-b7bU9sgD9pvW_9JgLi>JKB zrWIzTn%e`1G7rq9k8zG^yY^=Vu2Y|kvMtjl;{;dk)*`TO-QD7DRu-})e@sWjHl#!4 zH{DRf(8KZBw%x#c&)+NN0_PE8Wg4#1q@esaZ$zacK9VjOEy~DsW-M)U zw!6PiF>7t@@{|*~t^`Qt7{pL@)d`3;(wu)eE@+OJWK(E<+ac|_ujXmjcO4%U@x%2% z2+KZk(ZDGB57wo?SUO;MeBeF~QFSL_cmmDr?=KS0JE0(edRE`jN4M`l?8m`iY5e?e z=`|nXlOh1fmT%ttW`285DN_byw0+j;p^IqqYV_~LSk=&IcaQ(#UdGz zkuAuW-v_03gv`q@cs6e+X9XuZo5UMS69{~fN7UO9Ig;SmNfV7c7b_mBIVooc!Vo5J ztucI{ewP59izNj=!UcW1MgTG7%^ecvd{)V`v;an~&ymt=EGLq~@dj`}3|HHP>mrF= zMWO%fUMIDq=nR=tnSLRd8sM|?GgaK72AU#GQV?qk#fGJ+@N4eDry`uQXhaJkddsFFeL4EAynnfq6p9o)^Rc@wqCB?$N8Is8_5jU73Q&QRW-i83hUrUI)o9(PS1m;{;=e}lc7 z>M9GMh#aKy1O{doP|?Hyo9U$cb7dNda0OV%6>WT}cXDhF-Y zahjI9L!9O=V^!Q}r3W+wricpzNRyDp0@88 z!gXo1e3$pxIvHSH=7C?J@@E!wXRh@X;NJ@P^ORNs$12rBd8#65ajDzuDW5H1qyib(7aejt3f zrwPX2!(PI(L3*%=9ab!&~ z|E3<4N?ZS4Oh{u(v=1PKgbiDp$?(ebE4GqPO+P}Xpo@~f0e%GKyWgPqsTVipgAt~h z`L(bQr~`0VoYQ9+Tli@AEQ;YMq-Y%UnFlGo;7q>3*l0YKU2c#jVxi?urXg>ZWb+#O zs@amR+7gDS>?HaT#a$iThTAtr`a_7)ib_Kf$FlL%Z39H{S_H)WSloF12E(E5lG+We zpOQp1ok=hd^a?qhBGBEmoejqN0zMmbiva7U9Wbt;XRE;yIOC!h+s=W*QXcr>{1F78 zLcfkCe3X`*?r7_OVM$v}yu(dz8JeJFuK_X~020YJgHvDzPgAuJI~O-ciX8zW05dHR zFi~nHl&Ua7a2HMpF!OZEK0XLbM}tK!fB4z>zlR6(k$6}R(c0Efych`M0@dL=O8|7g zI6trR7}bh!=&d>$*0;D?DlDsG0TBlT+4Ei*Y8W;0@eZs9U@{fx+=G%Nsv-FmigQ zQ(o(EHWZt9>WB*sG0EtVqe zcx%}iyBv_zE5x>!Geay=$LOS664Kotfug&m9H?BB(H8UJzX09z@sFR(7MAv%d~zmz zdN$XQkyW-T6*@twl3@6a$r7dtT^$Cr;t)VL7-klI6u0YggHPjDfV?8G0*K-MejYyq z)&Y(&d-Nv~dMbAe^@%$f&v4B3xmZ?_JpCy+h764$9~D5G3m-og2q|eN0o(L69>m5- z+^JzKGyr~fKo$tbQ``#q88KH!>L4a3J^UowCRjRSMD4RwTwb3~T>_f=5(IN)(dmPa zE6sInXT#FXR8MwSz1G(Z*}ZQ0(;k-=$Fb^EIZ*Foatn`0gX8J*5S2;GUSzw>$9I+= z;vjks_$M!a6bgVrmJ9zlZrw_6?zaG~O`rvMz7^wZ8zHL@5<=g?0&%@HxpwK`RK;^c zoR{^EUzm_q&iyq9c>)B;x-rB%WxeQp@`rX4RRYHOb^-ozllcpEqiK_Nu@2>s&&@RY z+IJ?^Mf843PYDYT9Jl`Qm4)uUrrxjfr3~g_;7TD)u`$OwdX{I~ZjKALt(}KJXEisp zS}$my86=tLqMq(Jrh;K0t^zPg-tjV&Q6XTGW=H|Z8rb_DZ3`(0Ig@M*z^~oY-t(75si3Fi4*k}eU%7u-cBZn#1wEKd2_X5sQWHj@{I}~}!b4tDUQFVj5rervp!FfcZVJa?79Ltxc9S4qf(kYn z3}uStW+k2tN|Z0Kx@@8&$tA>hNppUv2zNwlR_Sw8b-J=13(uDm>O zdJ~Xtv~d47Tk&~+2O#U{oABEFG%K-}2!EIwDsDn1+8|8lFnPF>+T-v7&fgrNUzvyL zD3CY{nEl+Cqt@+#^dp!tc?*)v56BZ>%h2U3uGZoizjqNgQm5*wceDO=F< z>RgPG29g!>J!D<3Iw&mn$dka&OyN&dk z*cg_*o^p|z>k0UP5g}8o;}j*T%x1J1xrH#WDuEwWZwjkO(n+~-wF=qB|7DeA&2kPh z7Xs_sMBkMpB+O;+v`gk#E6B8lJ`6ueB#aj>m5Ga^(Zpq!_s?& zq5`pG{capf6CMcvr=(&~GjsSZ;>l$ydLBpc+8cLZ+DHhAnJeGI70GVZYO+Rwv zux15UvmP>(So=rDKZ)`whe{&(5}J#OHyp9Y>y%%uTHC3Wz7^wD)YTN*=q>%IGBMRi z(!|SO?8z{=FuJ}q317gxR|hs9PAgUye7UyLSN1M%XxAs z!-72XVP~}iq~jO!!nY~;ehKm0Q59_Bq-2{^9vNu{=37y`26lfS&6uun|0}fc?cb;i zhQ4?=1kn+~#UPIm{KL8oN#OQGH(YkoNZlE)r7*3Z6NcR^4St85_!g$cEw^HWxT9~o zXk=Bm8Eh;K&b6|$HjnB={K-{I^%O3w>14lB+~fdJmqw4DEJ+7vCW^buG$GJLL@i@? z_zsA<7IhlGu@0!a;n2MdR$IHRRG@cE2}jh$);*}vZyyi_XaXn%ZVpGW0;MK`=#523 zLx8xPQ$CS$+Ulu3Rtf#?W1g${3Y?UBXT2Mk56f=DqSn`(9{{~^<9%Y0fduvGvvRO7 z9LLEdehAzm%&g3D3s^Qt{FHJnK*sX`-yR$Zd{(cifDJ7(faqMU3KXJ(ld1b^p{ZVG zwbxt9qa*+jCCnBKSJnJEu#BY#Q3YoVSFFv6$#8I;u#bFPMi%RiBq+LH?A<#?f+m^R zlZsnltz;G-po}vvRgk#M$sF*Uy~RVG=B@Ubz-}VHAfcv^^2m3lIPj`XqMi&x7Z2|- ztUB#I}_}~nP<$tHrMmKC>Uebr(_hX;@(tKuiH?o;~5W2=Wc>*n(Ymc-9XEf z9tM>*h80r;U8SH7{gb zSClE5U>ZCSR4TWb;2d=k9fQSP6$U^djW;bPnV?o2n?>O*!UBU*% z_cvvMCEy7c&dL3}5xtp$1s9K94ave_4;(J4NzSZ0Qjuh^+$pC<7C}KPM<9DMOYw}WiL%5&}H;lJDM4dfe6R^uhdKV6MvN`)=dJCr!m~&TPhYv z5)qcqJ?nC+RSM@V#|BUv={-^1senW$!tEDmSyeC=2_SI+s*-17-WWeXiatz7ErGp@ z0(!PGH$vW=@Q`*_Mo{0jK~86ae;UZS2tC)=j<|2sn9R!qcTKr35FI2IYSn+B@Um15 z3KdwZo3@|4r$Buo3HaG3IR=MZVlL%b5C_AmE!nU(vl#`S?kzsIxmfB~}w(>g!o|9a`5G{V(Cid^eSbvlbV;=ZTJv8j0l?#TK+rHpw_H7Sy>*%X2x5 zkzbv<8VJY2i8yx)Jh>_w9?FX71;m?YZc_Qr`S}+BC-uB`&I@iQ!9<&wQQxOPCRK(e z={{56FR-p)7=_u`z;X4b>HyGAnM7d+b@57=mC%OIi`^AYxoP!wTZ=T~IPgJIydsEB zkvi!2OP4W%(MB27s(cDIaIl<*k3=0(i}$7*?jm=rn>PY^dUhhgf~vu#Wa%6*ll9Pv zKiG^{|xs)nURLz!sf3=*jJfIwD+th+VkZq@`m!6L!muV z!xtN~QG=OS1)|2q-`SAR>r-QSUsDOHm{pu8y+?DE>=J%E?0LHBwCL6*L<=dfvW0yG zW4SkC33MWF*S!lOXCv!7r(G@nBG2LM9!|_m@QRs~S~}mjCR=kR(sq{(tZW5JROD z2!K|Y6b41bIUf3Y@f_{ZGPih+j#qI=O$Yq&tCx7#y+(58s3dQlPJ5N{LmX!ht~;gG zE_ng|d^QeX^es$xLRK)s>K0LP@wp$>yH6QQXJ*=$P)FkhOFNs_FITH>#`?Ge25&zpVV3tQopoPKO7u;P;UP)b%iEZmc6A26yyrvD|?XG z$SiSBCs_$Z3>x;W9Yd7Y1=L&2OAGJUeK2N-lLm!VZIU)7J802~^&1+_qKc-7{?w;U zpHH+^74>duvq3K0stcfp+TLcc@Ek->prnx#PP>;;@iNA^szip^p!E}Y*-16F42|1< zt^r5r&>nwKryJsPC*yDK2^=n|x2r!U1aCX}lGeMn2m;Z`P-d+JVeXHeVJCXTS)g;2 zC=^LnuW!RfcaQ?)?F;NF_GcDzn~_LV5Nh4+7(d8x)B@8aqf|y^Fk`}qvllVU`3 zpLG(vNBNT+!n^g(VJ#JLa8eCf;ziPZ;$q*`EF?ybu)>_sR`_ALt7f0Pjx6UzYR+Cz zRsrna964UjqZE>RzC|~NcSvb{_G~_Niq`o`^Id1WB<|arXMci$N)~e|H&KP@mOd_s zQVhVU(KViY4Mwt&+-Hs~bFfnWpd(r@aUfO#Qcw}W81cP9?Oj-@fptx&zy-&(m$cbd zNeD=yC}Y3nUu42!yGG96tuXW45cnm664FIIk(kMDQ4ki`c+&y%xRsk`I53akrH7=Y z<>&j6xw~^%u_*#ieS8Q5bZ7xk0Ee?}a@G=-(5&yFrr7smR;~P+K#|-OYi1t3YN6Y!MP7+fm zgl_6+YYp7A)UoP_v3Ea?t+3`XNvXaG z^AYP>bs-QY+z{y0%MM4`b_JbTUb;_M_%n`QU=T$=Ig%APm-6FiZygFbOI6$-lLzqO zR1@hX`IX@v?6HQ45YF?FG`uT*!Xvs1bZ2N+4PV>)?N}Q0_cKOja=6jSt6|rgv|`b% zF9d7^DMr2|=P#!csMBr}f_vH+wuNP>ml{V*=B}pL5(LI^>m<1ln1L%MJGLo?5G*6I z9EgN< zMgWRxmCaNdV0VkY#0%tXRZ1AoY7o%{sALW1V{{!D)0@~aqr)sNVY0q^b!Cydc;nun z2OY$TP+~U_Sc3~;8`mf{HbC-WI28!iY)~{{H;Ko|MiS)BRFYOnMizI+m$hHTTSn#K z-KKzI1QL^kUoT&7t^?Gry~-pNx(U|R1A5tZrvAj+W3IT#T0P4wl1wwrniu1YU?huN z_O5<%g@Nmf{OEbhg!+?Np=mbg?_Hjy5(PAt>y?>vVppNW#}r|Uo0iNfAkfB-ozZuUX96mX;;bGbS>!_P9Y^a=kTrn}(n3*w;W1b_AZQzU@e&Hi5uCaK@P{Ff{S`UJd(dum;dwOE7z09~;>H_%)c`lGj-CPjvu;?I zf=|?G@G|b;EbV)EI*rf7!I{O;Ee|)y97Mku0;S@*YonT1alYynAukboGr~v}qZKGu zDIr%3o{frUs1Wbn=U9|4gtikg0o&g(&8FLJTt-`9NFE+Py@t}1Tt0{H7bAVAq%MYP zN(VIB4QQ6Iy4eTA2@NZGJW&Im)*5fGYqYa;*Tnc)fIDpL+Pdct4H)xChZ2&tt@awA zjb-s}mc&r_&@EGaYwh7j?rOSaqmn?E$pK*-a$fbQ z!`|3GvIeMCZ*gG*ilw0CKtn04xtcws8N8wV4pE35Q@&M3x)}tXc8AK|<)n}U3N7S@ z6U3}ys>BcjMyea)kiVVqPaKAgs_R`@jt}Inc?2!dw_NoBn3+o5A=DP*6C5iE0{}?= zl`4?x91_Xy*jaA#iiu|7Lj)tlX0(^W5*L;Rmlj#FF?l=oeZk>@|B(-ccp$M%r7=l> z9V>*U>7I1zZbVldr`2y6>;n1}Pr8b@o*MKPZI75s6aNG0avX=8y;)glJSolG@)+yt zrZe~j;Q$}pQDesh#g!oKOzms%obd5Y-Rz0fnVZ8EocfPqyK-w0&MJL*m|pF0~9G3P#@OVJ(p+f zhH11C1fBQ-ywny+cPVWhm<_)Iz6R_!7uc&E!Ih%+s2lHv&;2K%8cf=BcI0*z( z?hRY^!StW+IhZUkaT5*)VT#o#(jK~gne@sCVxvxB6VaPHrP2rx=j_?S8Yp79O(v3b z^9=J0M1~j44;j|}78E0cTftB=%q6fbxpYBhf>l$95hCXu{zN;SOX>A$O6u^EC5^zo zwJHqgeFY8PMfnN(EfU_XRp=TF8-~r9yoj`ZakUWqS;%bwwOr0*wD2(py#;y(5syIK z|KlZ)_4(lHQ63CY4uo0(0z6Is26}|y6(S%>5@oG-W63p0hDVrotn_;*;)*_zJ54Af zlTpL?a3XFJk=V3dQAMU}vqIK!mMAZ7+8H?_47HsuqXAC_u@&isNGK>jcF0VhGobv& z1P#)|Fm!HSHfzeNMgjNy`~W2nrm|pZTPmw0Sgq{vaG_@7k=>DhTq-77U1|A_JO+Os zfi~-U4*sn)u@k)9ZiDDOGPpA;keUO{sT)G9as|H0lp%ShN(GD=RVAF}aqqV>Ij8Z7 zIm#i>*8sTJgB|)?2RtQ#g)wlqtO(sxvq+1L_tVMWM-D*#g@R!nIi#vrz7NFQ7`EkaWCphDoabu7yK@e?u;&`m~p4TXRl zE+V5c8PzIfAI&M)Aw++9?*y_RNpp2*{vlu=+in=!_B$At6bdBn8I{G# zP|v@T;>_ek6;E2)6(az6WX(wNyp{I&j}JSPb;Yns|KdjQ;Pxj+q&f$ySp@%3ZY|GX zjJ>LDh2VjiwgQ4(F2x#}mM z&dmgXY4r$lw$&Cj7^XyKa}yK%C<7%;6^u9ME>}+F{DI0DF5X4}pBk?jXC6vE>JPy4 z-K#uhv>2m)D&ne%?JfordKq!w7%S!@b=%a zgA{s^Fh~C7R;N|>j4?7OUb+7wiq`+DZN%&le+x$1rLX5xE=grS^6{uh5r}HE z5QPLkIVr#durS4Xl@i;3E~|*6(sr5B)q>N(u*l;;TUf>4xfu{1u1SDp2PA1FmNl`1 z055LqVsC}V{!7KC!=~4v+y>LWp>GX=cOY+3ymtcAL#d4Oi2_O$*HHwRFn3>H~9ZHVhgKYY|407?qLzv&Q?T zwLN|w1^y?+;bb9v@(DZ#;TLGFXkKPCwO_rvlwtU;na+ESWXvXr#uWAUd~2ivyA>2sU8fBX zMF?7*_ZNr&lDSX9_{#LqzK@6`wrh943LgOmN9ML(T)KIZjx2Pb6_^F~6r`k!?cu~c zxMmcWZ5p+i2Xv_=^*v9QMF(Mh_{QfiF`OPX(1%N0Ls|OSDFyFN>%Su1R=3?2d=ibj zPkv5s@6M{(;3PFj(D95%Arfsbp21bFc};Yle9z{nyGzd9m@h1Bxmm6ug?=o#J{-*r zHanWBcHYy7($3w0JH7MFpr77SHaj68jAX^t9S?kBMqW=ETu=1h`#sydkFCr>S3kEl z-VeEM@Rua`s{`v>`b6i>YJ3CTHuWIh9S6_u29U`-BXq}f=*+nEh6mPWFfC6PJw@*q z;j7*oup#qH3ur`E#_GvEul|W^lm{#gKHKGlI4ghk35y|QW!q4x?CG?#nn!F5Ty&es zSqH&cydr3ElPUiddd_ZdJ>5#miK*q^gnpK=8C=Rg?jwa4c&KvV?GY5ZDY}U{HN!mP zfHK@lf@r-B$(rIt)*g3^y}bnV&Esx-UMQnuaLoWWK*+!S*F~R+OL)NzbwDQPp^R7$N+G+jNf%Jfvp-KT#Y)L*o134BWg_)6s6SSb&VLF zZ@B?qcEy+ip7;TOz9p`#vB8JE{&+I)j$IK%N8*R7f@KtXn)5~*S%(O{2x2NR`J6js zCf!ATWKY_;Rv9!&xKcT+hP;JU*6st6Hs)3A)c@LlnDWm=y5lm6ACrd#%VK`X*rNwS zM|%*BQjdK|1qS9XiUI2pR;>Nw!@l~el+kxlbW{%m{a*!!~0 z=*F599z!48`T%J{~f`Vb2O*KMl2FC`RBB+vapS-YQuLlfE zMtn<(|L*L2uz= z<~bxoXXTiFRushSTE+;9yn9#54BE`+R=Fwr>9cOG*-jKuu41?J)oI!br9HZB9V}Fyg(}vQfJ+Z*)GWz2t)&L0W=vlY*xG(UH|qvN-(Ml! z20C%p?@>)q?X9H&RoIZ-n)a3JX?HkmTK6JSSyn2JhHL!DQFuCqDnzpfqXNcJV(ej~ zLaxzOAa$Lm7b%MV zv!xn8HF{-KI(rIaH6-W#AwBiNB2y9N&I&VYyH~qmFO1+7F(B#$MLQ%X*#}*|WK$)| zb{TP!z~7i^4(y5a*u=E5%k#&czL_KN*!JPi%co+VRS}v2w@e`1E5qr%MOaIi@1Oc{ z8etb2*CX1Bqnkf{O|HM!=Yh4iU)%6uVj%Y(r%L@DS4^@@lkawNLql6^kTSrIlXGx$4Y)-XKhHm3VckWte3irsOR*f3ogjkq{5D@_(xA zG;o~Xtgb+E7SkrqZ;)R#70y#mF?i)Xr?(S^@s>`Pq z_7^b{qUR6n_5k>?V$;2EQ}HPH#9x7rt;vN%>*EVfVb0ceq=XyasDtQO!TOt@lX8DJ zYO6jDBx9*dEm-R(V-5aSlK28>95#3TWf*6vsgbpnmJH)a;Oz8`4A9Jgyro_LjgA5o zRgno@Gq3q}&@ToAU}3kh?8Zx&_%o+BTI@O2hfgCg?8HJ&D)w*!z5HMmOmA0=mp*<7 zcP)12&3=5E#fS&I#p|uzZEGhk2S^=G7#h-4Ns3(eeF8?3CQ4^=P_e80X;%tQ420pSX4yh{rptTrRt^(e zFehCww~%6fGccL*PJ$jCS(~I;N%XRi2DxdCkrTQ3+yIUS!V|CTAcj^!0@)V6KxqB6 zA<~5t)^;Mg+eZ9c94q21R6Y{43|;=<){`H@DtcnD!(TgSGFsXe|5W~{C4=|CFE-@o zH*MqQ!8RWVH#m>i*mTTecTGV<40J_?gt|>m%(C{wekF~oz}F=Pa7wK4BsH&CEfZ^G znc>72!Hc1!C6Ei-&Mb@85BwFksKq#Vd7x?UdE5$vFvqv2Ul0-$7x0d6@R8njQtcP| z2#ndSYY&s5Vw!SifOo!7GPds^4WCHiLW&m&+DHvj=5B3x>$HGI5Bsq5{~oA#zmL!= zEYq_)=w@O?zB2}_NH;Sr$>m(`Xrnrz>aHY3Ux##kXtGc~dYe#`F)t(EHfGQ#t5~sm z2@y-{D1{3gBH1Y3=D|le7-f?kn4SO=ZtjBg?8&}N0W}O9

*Lp9z-c8{EH>-8`$ z>bDTme&L$FFL-=#tMq|xXO%DQQS&lW$miIxQK+IMz*7Xx5$()zvseZQ+BI>yxn3|4 zlz3EdVVx#kB0J?=UZ^5MNHVCn#m8I{!BSl|AJSSj?Q)RMqGGPN0EiLu!08VPzyTT9^@oN3gR3kqJaqyRr_Zzw!MJq{25YYEQH7MGI$(ANJn?P9@7Aq zEfiLPY-sRY==wTMti4bE$+ZZV^HviSJCK)_^8SmV; z1RCuV;5Bu9zF7kRc7bG{?G~JC!#qc< z0xJiPbc&hW^DGcPO)STaSmrEIpDO#}cg`nb)q1VazfKp!u5}}w=QGH zpRHR-kb~8@+ErAgSUehI4Zdrql^M9mBW!Gh0nX6#w9;_dVZy#HtHk=%Q)w8rb1gdE zc1Ew3lU9fu{p8T>ucO0_uVT#qmM9=rrK%TD!EBVqkS?g9yHf$kyAg{$m%8@qYwPWQ z1D^EL-;S@ZuiS}y$@#KC1O?ya#m*!yU!+Ldto9{87CicW3QSMo)&FE$_zOQ zF|$OAaC8B7i1Yp%a-6z4NB$A1`eedSSAXZdXC;QBzDGtiS9%~JQrE`KljL!Uf4>pc z)u+Um%C!rJ>(Tzp)9!1=Kn9)BqV^cjZ3JncdT<2(%X+8G#_I(b6@R1R2HeO(A~FCc zgzp{H8rWi}&~+nYF`ix7_c!&F#*=$H)*C!5rB#)i#I!i1S5-c3xX~juJ@OOR@w8xP6DUD;CV@#N<4O-i}_4=figAi!lVSW(%oH)I&`5S$;yzBxPZDENGlLe1%L~L z0bFF3l-GzB^AMn%07k9{s3f=58;l{cv5FGY!p^8C)Y~D*(R zC(W0;I@ku`UJOy5XG52)1ULsG36sg=%{-ZVwkAgD`}PF)BUKSqqdYK{WZ21gJwJlm zaQYl5~Ie~;e`dVUy=xXWp#QJ6pZwhE{XIALFyHHG1obfgSn+TfDVImdo^_0qOHph z=JmdQu>B5S=#UW<-pT1X_#Q;0TQL2ki^;>(Qu6~F$|&K8iu#T!C&LvJb?esYFTokk zurYMi9-2DA`g4-3_>!hdAZ^{*Ox6Hh4%j3d<d7k2} zo#Yk5$%byHO{froq_GVY`;Jp3$l zm$207U54;8j8f5B0;f=~0AfEEzorY#7XQPdrd_;f4aKxrBnwks^A9C!2 z!Hu$CwoHzP)M{tme8WR+_c2;WCy*%#oY^D#D9xWexs=@zWEQgPd=>RQ-Z)Ex@|ALs zE&FdtN|P1rJSxO|HnlT-Qqzo4aGiaEDe6SYo!Di}2VG3wj?N92aHC?!qHqgY0J>c2 z!fyK}bAv+kRuw7IzjG|&(bpR0HP3aolA2l{7%>1o!fn;IaBXr$Q>xwZfZy;iDOm&I z%)d`jEzXVk)dLX{>j7+aAEc%o=meZ+`Tf%e<);f1m4^M@PAD@=M$YJ=y>(~+wHbq*g1L99=y%9Mp+c)@c znPnC4pe)6pt~0g1LG^UYvkR0+swID1CSvQ7#9PrIw2O0sw>j=8D?Q~Va*BF>I!B&O;-EaP2O71 zWCF9uTF>vRSn4YC;RS>UTvrlOk!>R7ojoI%AOc}JPR0xA2XjyJ#3oL6gJ3K z^Ku;qqsAz&8iIm6Xg*f44%-kRxUFb;1rHz9C}PWJewgMx3VLPG&Sa~g#^vu*z%mw|^_QvVY_&VWe#-r6{X=JYS~;~xw%S_1TM8~+`e z={4@wZ^)ZJVkV>kSpI_&B>Uz!&JH3VWOCVlZv<*zbB*pU2)V?m#t_vw_r9#H+>MFf zHv${<;=&fR1S=`BXPRK+8;aZm!PmeD!HnSkkD-H6CS4vp?c;r}o9$Q61%m{eh%6?g zZ|(#nFYMOff4F3QiD1opieD6>o!3wMjDo*zP!>pR+ifqBN(O2{=;AsSw635r! z7F!3U1}V`jlUH+dF4bZ1rF74#J`jUpFryU9go!nK!sWOg8&-6YAl8EN8KTfgdn#^3 z+u$!JgU1uCWy2n(a2C`dMeoQ@mgMZsJlGAT@WRSYbpY>Q%bf%Xtw>rN^rOb&)9i}H zx1}k>>-D-8XrfAAM`PLvJ!=PsSjyw%zLO!gQjI=ELv4!7w6l>LL_>!m*acHjw{h`7C7-;*}p+Zt3e8wKgTEu@g13 z+k5+cp}K^6>)Qf^Y#XNlhizDm?D{7Ob%i{gW5^fe0rXc2DUg|ZhQ$W;q){F_w#DIa zy7DD9y#nIzq9R0UvN4QdSR&^R7k?XEt#VvPlf7xq@n?fMJogm?kI8Tu0}~2R5K6Io zUq%ykRS&4K0MF1NBc7=b+~*n;iXrUhl z_w0eDSjz*g$-!|*81VymPGXhbPR$n=m}vn(;SgOIFBqT9Tq8yU)V-m8SgQm1oT39u zZkDg<8tS#4PrD=-aH~;y-$Z(RVgnPc%=!<*WIA#kyHGQ|b!p+WI#V1>f_E!{l+d^p zuzDVLXN47d(S9noCP%Z;TUIN)3oW}7jtw4NNMUj_sLASrd?f<*YoJf>C6BJ|$t%;S zM;;R9^VJo_>{`IjPoQqD)VKx9lAm`j!j{N)1zPh~NH2s444uzlS!FNbEm^IpiP{(W z-y;jff*CVmNhjDThdP8Sg9?HI<92;YTSs&2FM=6tnt zg0A2-r}3DUbchQnWRB1Zem`sYlM;+NwBz!C%B7-=Z?RKdMmICpuq_M6vX%Ak)*nrz z#`%!_fh!n2A5Yz^w8I;jY6I`S73xuvr2a>Cq>hWG{m)p0cN)8KU>j|WQHDeUGi zA^Y(&13aHkBtSYk<8BUrf>{EhZXH5)e--VXT&dhfmm8Js9uswW31o5JB~PL z7$?fsyB#%-pg+Q;nMItu)fkX~{t~pE0e=#b$n>zQ7E_msB;a@7_U|r0EZv@a7GJdJ zN~?ZPsv{$jk(@e`ev}+wf+MX^*HVQLq@}v@ja{H3yp6)JaeE~Quz&+?Vb?N%ZF0Nj z3E5JdIVfOG^N zxNm{|ajM=_9Nb;tHhts-T+Fu6Dz_g&NT#j509PWlrw$WH=rrnY1` z0P-5#5wY(zVc6q=Ix+yDvMfPT(02rZf&TLKXgHhnPkk0`+w*yR;@ewiT zXpHI8(7yvW@mdE2m2fv7K*4O#KyMqKUfr}sj`jC^KNM&++W_zT4^{OzJ@AQE!}%w& z%>W9umUduW3FOwUF!N%wfG8jRv}vLE#O6nzolXoaG%FoN-YiA$I{*#{2^R+da%tNE zNak^Gy9Ob~#msXw>~9X-;K#j!#*1i(Ksn=c&KOejf<`4qIRq!sl*aW{P|FZwQWP0p zq&(CgWs?espQqUGZ=D|NM2@V)9g$h6Z50&l0NJ-ks*fUf&P$1Vz|J7WTFkNF0_D}j zztd7JETR$I9v?;Y?-^dEJz8t#6gW3 zg-TKE{2>T_TQ-bDB{Cc?v!E+5c1JxAM{ei*Cadr4Bqic~!@^P1Qe2>3X#69K{C%b7!Vnifi0>q!DD^e2c=s95Q`SOfCUrCvoSZW2* zb4%opynW}lZB&+_AN_j@PGoa0*$*-&ZIkMCdN?8zJ4-GH3b?i^+PyFhT8t%PHgd(_ ziV4y0bxK9d7|kUW2mH2JActe^=)yoPR~f`y3dte+CmFShS8alSSlkdH>8~F?j7mT( zNri=iT(=BzUe7=@cCkSE-l@>DPw*s~Hh&LAuDp=i1-(&Scc>2-ANu^C>gec8b;x4G z1wiP_XAI}fXg-xg z9|j87dUgoTy$HkTY-G3JVgwV)7ZBE^eYJ4J*3b+3jKmk!x@&(6Igyz52F=5NlE0^4QzaQmP8rXG3NvfJD%B00YwY#NceG`@)UAO z1eS0&xipMK7U+H(_uwSKkM-XXnC%5Wb_!}+L=bVUHsr9BsL%=(QF){i=O1)Sh%sZw znb!e@u1|~+tSWk^F4#D^MKQT?+huN?{FL%)J%&96o?_xrkJZ?x)#+_UJO-Xy7IeYA(QgeU=pnjC3sE0E67)%TZ_aDHDueT=Px$-> zcAM|;h{_liht*XgR+i}kb~^KJ^=7*jxmy$}@QN7KfcZl0pi2$;zlevt3rppJ^!|Y+ zv6Y`N)M9wOXiTVh({OGAS5qNsv1qS{qK4XrUHS3#z!DY4)pMg^moXYwzlB_FdI%GY zyam)Lk4|S|M;_eGrhP0O+4~v^eoCok96^)z*Tn!x?8Ao-TrJ(^nAq-U*8(*6O7{ok zgmTq#Y1PpWqlgOe+}}AoGIzNx1$p*8r;QvN0Eo}XV}!`*vU<$SLc$D~lTQaYE6}bg z?cqMuM`(fucElTKztE~VGqE=v!ECp*V|P;LJ>MM8i9<*SG^QLyJf-L z9f-@OXCYoTJX6yO?=WN*4z-02>h)o%rd2ZRfOCHse-P}w~uLeOJn>xS(gDZ%jr81 zMDGZJI6o7U8Du3N^%}>$K<)%H;;)~uqy1vGQ|mhkfZPN6kxX+ygT(>|atguIyNT!J z0+aX-GF7DlNoF+5HB&P^rG6@KL_iGoM$V_XCz^sV)1*a3acW4W7FZr^;|x3>bC>g zMQHV|FUcGWpWg~GguWWD3>NVwYWDs`aMPYlVMmIav+4a}z+kq;U~0IUG<*RzkB=Lz zzMOHI_RM|mU8Ld%<_gw&FVK@xRVzHeA!igB<64=gz!=ozoNplJ)yQJ+W#aZ!JFWAy zst&6Dg5)3M{^l?C!7+s5d@y~ldxj`8h(7r0&NT8bT~X57tO5s?QRfpqt7bOn>4BTH zYe;gtRT9Q&IZl>?TOpt(9gGY8Q7|V$%XJ~@kU)6`-*lnaQ_hnD%KOfiww`c05J@Dm z!>ZRG0;k~V6p=vSWZR#k<{_DZU;b!4Gs`J1^rsN_s8NjFzEe||DQ1vng@<-cZ_+qM z{Jbo@{8U%b-{%E9OvUBG`j<$6(>K!5gSn_+whB9H)R!-)z~|4LnIZx>2>X^dI|brp zCEUd*;q7Xx@%IB-QRjNrxH&N*K&T*WTbK*>56%2K(Ka0T7tJWY8ms6kMP({gRN89Q z)1)3y;kNGoRZnBd5_Rzs*HRDqBfpN)zvKTVq9GhWt+)lB6O*h+$MC1XUbm%u7y3F1 zy~od(4?F0XF;IB*S{4>nJI;bKi@xawF3p}I7dJGD+l2b$#bnSR0#FWTFK`nE5Zf$z zD|2nVvc(CX>Hm`JSn@0~A`I(~%bS|>d$0^H7e}4$a;+3@;Ta<8p(F{arB@I2t^59| zrN^@H9I^#AVKmjym`1nOwb%14i|n{Vf4#)c)kk*wWk z6BdC>_Es%T!I%N6A-eHS&u$IELY3jc>gI}<+~sL~H>MNG$a^A_Wv&Y|EJ;ON=N}c; zpfj|ZUeBp5(CZ@E^;-?$&_bUml6Md$)JpD3rw4=oNpk(WT<0Ie;h`$SGzxXf^!(2f z{@f!mN}d991hX4|LmeS=)$YiPLMTOek79ityFY^P6GR}v3Z(|0B=$Rbm~owkfm7^O4KzHas?V|+9eS8BcG50NmqxbR~gC>k@w4+d!3g2YEVK%g$lB()>9B8XOMt9b8@f;=jbXe zY99{Is53dWlBXQ}BZ_?*;T;v6^#r2M7yJv%zVW1_>nZPl?XqAn3y0Ph9a=nk(^vqy zU*@4}v|d>MwQ)zwzM)?*$H<1(ZFD5j5v&(0Plg(#J|ieYh-%KN3WOt4TQy&I)=q;k z*LpYK%36BXtWF>D=n4_uc99m2MS~N-aq)(keeUMVjPu=ski?YztNb6_p#%QO5D)eO zl#;+gM!A3sV*ZTM_aOq9f^l7b;|T&Ob2X~Muq9r1dEcx}k>WzWPRqDx*Dqa4wH<;d z<~bXx+odQQx~)9Vib3hU366`Fb~9SumUB#RvW_nmt05yqkTI1&RFF1r-MZCmy0@iw zcVt$cFTjVnl*5c&o!SK?$+)62WnrBg{T(4*pd6`Ac;VSchKE`nNhg+hBD$551p z-iV1av`8uV15cUz$vJk?fVzWvV%{(V!(XNyn`R0* zGH)A-OG#vvy)?d=@!x(@1Ta1g;U_ZT2-~nkx@#QAs-!zSG>c=d8xR4Y*DUem zI0%L)cT-MlEoc+Qg!Nc`0{xs~$VtyHyQXWu%=#jQrbBwp@y8bQEM^fW*MP|oBjtQo zvm@(tdxKNav~T8iCn@ z&OQrm6g$Cgw~Xpsu1%OKvV9s%Y`K7*70;I}4VL-RCu;@FLkr0$HFAXUfbhd|KK(-H zRjENWbPE4Yz|T3nIX(b05aD!0;TeZUva+vE-looiIZICK1Zy7FALJrHZk7expezgE z$&rrlS>CnMpEX%WCVC@En^%EInRh*|@RI%ZUq$4>qxDC3VNf}SW6u19 z4>;Y>e0>6uOqp!U9Cq0rU~x*aFPYg|W+P+8_CRMOoR5^yiwh8RJ<5|@YZicAl%+EK z!?U6#sN$#TA`wHE^CAU!F8UL0bVzCn?dOa8`wuZXo_<0z`e=c!=uSmL7(mRcaBCsi zK|R!5yX|d-G)mW@^Ri+lhuLNFl$zxf=O-dy$BG6MlLr??KszoYT9D)gV_^hDDT z%m|^hZGyK(Rz3|VjrmvoVAu5Dq^W>`p$#pe%>b<#H91p|y3B4@E*k@#;a>~9ss-j~ z8s>x1yM>>mMr+mbC7aF6)XjsL*SZtvmIwFnEoE43fE6lEdG)r)PV$jz$y0KHF`6=?2^;)!j7Qx&oMv~gJCbX5B1a#soZgxRwQI@=jH{VFmxnvD%*1O77TPei4; z(;>)m!HW14oz&C3b3YggVcu09Rj*UU;Y!JwDPKf8BB8bI<@f|KQ{I-MnN1|VLlDWY zC57Mxks%aFRH2oMY`@0TPUnm}wP-PBbWa&yFrz>>xk6_jAhV<5P5I2W|MQKDC}zt= zcr;G7poSi(L~i>B(xTIS!NLd*!bLN&*;uvoXh4lm5IH_}k}m=RW&B-cWETqG<-R*_qO9r|QP!>xdcJf5 zn<{>j;AnM8glPJ@qk137-21#@rSy&`@3Nk0xenj9JjK)J@4X_g$wOF!EV2Sy5=G&O zD}yxgz#TtK5ek9w)XB5>D|$q%8jy)9Cy5B(BB1Qu$EYiP_=d=t8r9F4YY9fPR0am+ z!L!j#?qm#@yF+YJ3P;%KGU9Xg&~2=Gs}_h(r&fSJ3>_sFUziU_DGS18O}{>LA_+$w zZZF44G3r<1z>@}niYx8&E|Gsq zq&dDUt;fzch1Qhdh78bfOc9zL;WODFu}V_pe7hsa$@3He!|BX|2^(d&`x12CrBHu?XyD&cnKo_X8OpKtQ$rr}|II8T;vnd= z==G~j2$k1UoLG6mogLNOvz25mggy>PFw0qHDtVPgM3t}DYOnds@shG5tBapTEJ5gY0?7$-qPmYhsXfD69VVSq3a~~7a3n~FIfCgnjsA^ zmw7Z-X6dI_e}?a$%~!P>E`$ZYcGcg830bI|p@%>V#(&iY0eo}8c3!&TAzBtEwxSQ# zNX3OgjRx<=GA+jZg@Gp)u8zaj#H?m5?Tdr%`_J4tZzPbale!wPK+1Fe{Ht0Jee_9yy}vPuy3jjPU-=r#AT!hlu0nX6>13&Z$pn!W zjYJ-)vjAA(dV)~&?6?jpj?>35H+Js86BeqweO~uIhoA$M(A+#i-)UkV98MV(FKtLM zXeftw7m`~X3%>jwbN98x*hvq`%y=Z-OY94zOUf61P z`k?t6V1{;9f4BcPWw!D2I9DEBNP|}aC$Zj0h~687hzA9RK*PE6 zLvjHKh|>#%hmA34(N|!RL3{U^up=X1Mb`@bWk02!pl(b_nH(44)!#yVc@v3AfHq80 z**OZf5T+uLYDVS3|B5}|J7@UM&0|okAxHxRXp*IwKJx;Srb9Xp%F5ImLbEY0!*M(QVJ}~<5rKLdP)D45T$Iv?mJ3rSZ2yWr_f_&D(+Mmu&?pz1KN{;;TBpm(>f8j`El;CksN_BDjPSi??&Ne6kQ} z`)JEg=CVe5!z>7;X~2Yl`SBf31-$)#$01e@4)s@f;9u8tU%2y(Q&Wu1oyGv4t3NgrH3C$uqeC$CnhVt z8A}DJVakC#yPB9>XgnuD*r%VAC+j_<`9Kt?wLkrPbPo>M5Os2JB{TP7+OBbqjZZts zWv_uf0>;uqpa{-X6yFr~=)k2OVNiAo*s>x{qy3pG{`201#Spg71`{o`~K-&Nl1Z;w>*;JIwwQjR6d0zaJQss%U zW~UX}!V3=WEL6{%L!oj1!$ad0a>}Qa+qCa~hQlVg(Morg!Lk;(3j&wRoQy7{y;Zvs zPIY|LxEfmxV4l+3pnR3SDUKix?7@r&PmnNE<*XsZlF5)%ENSQeq-E>zu4KeZQ#={5 z>z`DWZ}7Or2`OZHWv)_DT_CJ(p4#^F;MGYE1rP<7c3E_71wP&`3-v;~8xO=F*i)AS zLNb=h8;E#&JkSMNNS`t!d;m}vn45c^jMMIP)m-`id7wm(%G0M%w8ReyDaj93TpsUt zJad_i$RvjQIrD1Og%C|VSsPt|{s954yZizpsIO+2aD;b;?;cVHla6GIAt_5yHlzfB z!%7IpfK0*NK4<%eN@|AS&O+Fk|0Ydfj?`l@s$0-5}xe(Akhd)x&Ksz$qI zDI5H^&Z{>e=Q*|R@Eg(fuQ*k&PPsU$F*qal)5Nq@TC74o8+TY6~H&3M)+~la{Bgps{$@ zbDZ+{C^i8W`GE(?lMa`4)oBQtb*9Dep0^^-zdHn-UP2R8ez^-tEc|Q1=@XrUQD6jf zyR%eUwB}aBWy{@F-AkuNIU7+Gfh5Qea64Rs(oxfg8I-Y z_r#FK>!NWTTv=@{mCP~vbhrvmt4So3)SiSh0}J*c4R-Zy7SNfll|XKx4Hu13%#4|; zKnEkOX#+L>+Z6r8&_a-T_XQwEMGD-U<{0))`L>0dBv}Rqj5P!7xD>p{$GLiU5?9|~ zTu)Njj&;15M*lEc$$=wKv8=W4s8d-+3)&!vmt-T%|xIo1Z%v|9vlNwpw1sYbwL|B{!5UU*WE{5hoO876>S6Nl7?7a&2Sv= zumGAAdzC{S&+4|unmFnO=yl)Z^%8|RjFNr_6isqspV((SCjelxxrho>mXY3Y=O3vwBH`7u>kt${@c2TbKH;_sS|d6buo z8X6L3XiyK`M!mmIX&>FjgZx0&7A1?{{&WGag32$KpottkoD3lvG()-09st&(o_1UT zat)o^sD6ajT-s-)SZEKt1qg35 z0b$*#g-1a^E6K`PXP?i)!1u!bAge^`XP@TC{z7!ivb#she-*C zf~cB2TM`C%3_Q=sN!cg_FB6s*r29+T#BP6?G7=i|DY*CAJd`|g5#P70AfrLR2LD^E zIml|2^L9y~4pt6gCJm~kEKY)zv9pdVO}zF4m9*(EH;`T!=6OP06AKna&hi`yiJ8i_ zTqGQ!yZsAZb(&@<@W{V;$>l<19%*WgjDaUG2pZ( zU9N~~$b>S5PIkHIOQ9UP6=%8gQ=WRC-f6Tk=N|uMe|df~`Vh@5`A|eGa;^dE$1epa z=I98JF=K?q$cy(12hY5o+T5cv0@v=o&L#)+3Wtu$-1S@7u#w4(0qyZ1o6i{*^NNh}#Djyy}`+hx4TN8#1#DhOM zB`$}z+tZgc)5ejPm#c3I5nUAlftpH7CW)1<9CSN3tC0IcSgK6s`05RuyxLZPGMWw| z>sGk>siA#%;RygV%*6bb-f@s{m?CX;!nA=oSG*Zn^p@EqUz}+m&sN8IeJ0amJ1_$-L*?r7MUxz` z9*xulE|;UcIO*X5Y{g~+vI#$g@f8sj|C~W*Ps7z-bARp>4OkB0@tcxC;sR$8yZHC5 zT&V~)3ylEto(ayd`8=mIAanbFXcU%%H7RYuaS(Nv>vuWKfATyBwo^WpVYj~*Z z;vpYAcqZNZe__c`s0hH>pQp(=A@V>SIRa>*)K^zh$(SNYyB?R%I~0dj zm#CXhIE=C5c z0?84q zJLVBL5IW=%=8<6yexnEd<2BZjdsgsH(%b=%I%NP*g~i1Lypm<>7E>z`t5$epgVwgG zJ2PZ&wfs#s=CI{IVLuw7_AjM5hv4HFt^>0q``#}cuY^?VTE`wLrh>PhW}pkbr+SbY z7#9qD-p9&Vs^ktBkvPCtB#b^wx)wIznJjQxz!5j(H8Ll(~$gJ(Y*EY`>tXZthEf-&)j30gpj7otpg8_ zpR^?6wV0LLfnUay4ErA+i5w4})$-v`|B?itlpHDw2q4{CA?|?p!7WJS)eT@!98eo9 zP1ch9?7fG1V(&`=6b~bfx#R5`Ux93HVNfPH#Vriz0DWz5%%%Y*shH|Y>QKK6m!mk%=606!?}JAE(*l`zt|uph0G)^ zvBk!`W3H_N>6o1jqw{$+1E2ZjU<2BvUR4#QtQc2P2H5nLK4{3G$ zPB4IgYMNWPKq#8Ee|I?Bl=Ld2m+5avNIw{t4K}<>H)i7Fz{^0{H?UYFHZlDl#sohq zZR5TW6T%%gc>k1w03iZ9E|7@ulwnz};oN${J;d7gR9npV^$J((Yp)p_0N1$YS~-Vt~5;X$d9e=u|I=w8kg0edJF)_ zWwD8qR`K1O8A4QL$W1w(pj(#F>;)qnExi!%V1WrK2Fzc)-&PdGyg~-HWfddJ!Wg@* zZ8nK0fGRRqRQMe1giVAU{9%nNuK7Mk+-5AB4ieTc&?xK85`R~mfYt~#>=@3;FctQgF_)k`B=L(Y)bYgIPbNInvbzZRR4=yk>G2XDf2fR z>{0}fAC6Rna~V&v(?T*E5Q2wz186EI9XD2q(aru%a3E6*5pYDH5MQTaX>+3mF ze5S$F=YL+Um->WH>%b9Ezd%PA7UJEL(BTt>Qns92e^&;#Mw@IpZWYq;hXNNSq9&YO zGwG!E9IE5}wzxJ-Mu*mlN@oCyyxv^>5sw3x>T<#8d*Cm4_jvvk&^;=V7@-8~32ujO zuu`k?{SFs_K;AbYaTkEsznhF#9!ZIYTPqU8vgp#6+V-INSatXW_BKZRHy;R@Anq0cCBsHyj)RE<8+EQAw52%glie;tNhL{@L zLa|%J2ZB5D;`YKt4ZqJgEAxJ}_qM0lz|8C?p&}#EIhD{axR~hjL(t2vs;C%e_SH8Ifyu30@#O zZVM8G?ECLrH>N4ukX>%8S>FjO2k66<7Pgj+s%xk!0KoB-^!k{C8^u;9|85$ATi_Z3 zUHkwpXC$`l!F3*}N}>9>la$ZhYIBHhXy^%sgFp@~IW43IgYO@0PP1oF+R6pgo{=%1uEoF#$>#8qNf5Lit(~N+-bf z!|ph+&gfcW$7L5BIu@y&hr+%ZUW)>V{S3SJHsvxPc<44G2MKQ;rxfQw;H#ZL&WmN7 zk~APRr-lJdR|#UN)_3n=b@xCv`xGv{nYMuZz|`VmN+uo^&7L|%*+c+GK)Ao8a~kmo zE(dYIErK~rO`NsT!nOoNQ{|Q&f7w4NY3yFwlMg7`^LMplVQ(i0y8dw{K7)#AX8mg# zE(m5S$}%(k*ye5sSs??s^e^0A9NJ)B3ilPwSJqt|k)Jnc>mR+fi@%tn%faue9_q=$ z+m-VjMIxp$*gaZ%>xgJ-!GXhPpiokxxfyy>I$pJe!Afnw)=I2D5W;H^r`Y1gbl*4Fa2Fv2x4o8s~ z(0AIxs-}Dz!3QaDc7Fg%i~GC)SogCH>O^1UdEM zOm*&#wlN$|l!B@$=vR;_R2=q+XfF7-Fp5~2;wMiYc2Dj3R_Fo|E~^*@lc3{^km&jI zoPNF)B#Vb<*M7SNz0-s6XM!Tuz=~&@(4~>D!X(vD{Q-37H_vCrFZ6C9vG)REJ5@G< z{13M;Y%yU?-_b+6U(TKqX~!6OQV1Cj@pRd@Dp|hjrUWq*3ga#HeMU@9PjsQzQ1LrN z5F^qUXW{Oi^}M$v(N<>-X5$Ls#x+4$e}nv#Qnwmd=6R^Zmp(@5VBk>Vc$Wee9`=`dVHRvU1Au z(>4xzL3PJ#GG(ZIftFB(l;wi+#TaOa6pZQ=NA+5^iNN@!(-Yl?7ckMTemB2-RVs38 zAuwgKYxML61BeHVj)K0>W76z;xD9#*aCEn*^iHuNh<;K3GZ2;*wPK2Or`g@T6;@^< ztwE90w!HJle3ETIIyRnz>Z*hV>R)+V`xkf?#2%%r(`9L{cm)l9ZnRbB4lGC|s2Q{g zH?5&FmB$qj-R0~aaD#O}>wR^QKT*ojoPdyHv--g$70GzXQw*SVeBeGtZ+N73>d+Sc zG&o2kJi45fE3soAPVW-k+>a;8Z@~(4L}_Jfa}%o{(=hqWWYGH8t!M@VGHr*b2n4J-l;TUsQU+=re6{nsN3xUJ?fWxW;2r?|``bb+@9 z$0Zp8Ot`$GP$nIObor}M)1++rpaG*3s3l%PB$U>=Q@y|(ww9 zI^zpFv4gV(1U{o=Vlt5nO!Z0pc-`Z;X_REMr18eW!sL@FvLH@ixS0J^jM0%Hsb9z; zg;aqaa;Zh=A>%Gc$&()ehef74VE)FETm6gxm&mCV^^5k#kEa;*m;i6--###a7bq|( z`~dcGi&?q?`R6=Awg>3Y&0exH6W$FRV4W>jGY@I1!6u7^5By0Da-Jep>BlE+@GGX< z6NZQ_`qF88Et4DlT1|8SdwEs@JibCNiS?XH9ri<#Q1}E#OH(LXn=wMr47}2{D?*bH zqA59XitKw{MOU-^nF0DRJe`p)_@UJ>h-{OI;%DsvnY)UJNDX0mz-dJA9=}QT)G{(yJb&1H^~8@Qji#mzaG5 zru(D~{$`3Jt*S#AmXVbjhShI(!EDlLdlqN1z`pPftW-J!B~(ap+M7EEE~6nfC4;|^7!&Y)5K;`F) z_xcZfq{h^lIL|GI2-Rc+k+dH3F+F5jS zQVtYQ6gr5T2N1XsT0&izx(0<7VBCAQKuE&%giz8cO6V+X$LAIC!`HJD7+4=CubQD2 z_U3~p4<={r<7I}3#Ge{@oM!^jWKy{=<4XHYqC}Pv{Ss7W-jARe))W z_?J$D3Se3IKUv!i6NE-)e5rSnuZLI&9+@EuAEJ;gDP~{-Lx*eV8eQBFHhCGNM3vCK z?4e4TlpmzC$ zvqGx?cgyp&Cl9JeQ=3Qb*4A|+Yzk(DL!Vt0udWZ?v+tyhR2~$xlDmO3Edd~=qVLoI z8ZQB9^A-6ajF8p*N#yS_D;$Q<(#zVr(}Qu+l$y?uloLo=bs~8yP3XI_nDW z+OH)@07GAI&klP~yUmw&D+wnGR-+l*I{kGrI*AyTwK)ArDB`JKmwPt>2iZiJVokse zbmvbDt1mG*p;>{Qm#`U?z`3x=v-(fHgZI^ZK`y%jD>U>>h{M~>;FYlZcNVl$tLk^* zd&0MI;P4GuXa0aBuADs!1CnO@e<5-W@qxh-1i|2~$B^>qBAtv*>9AoFQCGM`kK9Kd zr_J}@1$}B`*QSN7p@fVrF~|Emon)y5C2OFFBXvQw5SFQ>Kt8j}rbT|DPQ!Idhh33` z6fR@|yNlV0l6}Joy18NJVrTzNWr=q0w9vR`(c&&f?M5utEq<@VhK`YrF1N|3g zmLc+vgF!7^)$Am_uUp&96NDoxk^;B5%rDs(&Cf|~)b~1}S9*cQLc z%Je&Af!sUV`A=%(nsFa8N*mDihJrUi$aqN0=Ig@dC32Tc;>Ce8`LTsVK; z+~2N1pc>=gaE|-dY}jzhZ(KhFwr=U9yo~qCZ=v=gi@FMYeZTxN6=RURn9v5r?)116 z=2Al>BOJo*wrmdKcrzFm`z>`kjEMeRVB?<#dQQ>`0RQ^7Nm~k0G2WnrPmg#4wXge9 z0l^5pk=7BD(h%(w9{*&5iyzj(X0W*03D{WR2Vj#w*dYLO(SgN>TYtd} z`74APo7tH5h!Xl%D(BSg>GZq;!^X)TF6}IQ^o|@y{K`kHDm8%9^UHy~W`TxJhb~*D1Idi-?>z(o-9p}9mZ#x26om~NtcO0ny{`k|r z5zm1h&VZ}-=vEhW{@LRpWAIQ!-O`s4B5Ah)C%U9&Hf2GcSSC3MGbW0GG6gXJ+n2Bo zM32g0s?!{HpWW! zRoXmoTcw^Op0j}eP(Oohgjs%j2xx_`=X{uyARNIfB`38wx5>sN7cqhcip6k*0z&StS7ErP!Gq-+(Bh)VsM(?^9Ea*FdvJui;W#-V_>m{jN{aApiAmf9~!F7Bo zUC+rA>2Eq-_hu%cj--Xd6&Gn`7;i36c&p8f@^|=eg51>#U6#E|^FR4}_z#_n32Mko zi^D!H!T6wZY-NN4b=}Jb<9d+ZNLp*`LdlrkK`N91ic=cZl{_D>qX_0Q=ZhxG9?b-e`q3^PxEkt~ZwwgmK+GdZXU zJ9XWPTQIpGIdJfN_LlOA%_nqG;&i3MdPiyPIG(JYo@7O87rd+wth}GN6kPyzGs93> z|D?F(T&x+WlBF$@JSX1*OuMTh_Aga0D)BABb54m+ODeFF901NK`jm-TvXPc56W)*` zsrNw1^qsqy2seSM-ARCyT1u-Fa!|F9GL8t%N100gboo_^Wa?hd zvwa*~O&tRFSnkv#Dd@=`KHiXqTfS^|jta3mUJ8G|E|WNmYhpE0RD6sZux`8(R^EX8vadM~Vwzdr{PZXYt0L$hVdwHB zJKqyQgZGu@`ob|TyumsHIg-}SbY8k$x5`G9%>Bao3UAMPZ*f3QbKA9q>}+TYj#)ZB z0_evm-`GR?m@b-g8|J5Z;$yjWQxpaW`W^Hlb>n~B?CR&f=*&?*gP$mlG2@EJH#d9! zc#_+X^)T%hU?xd#HCUN?1^#NV0;EFtfvWH9t3^MD(&*DG&#gZYRf35ye&I;t9}9nI z^7tTpR7b?>SBOd1*^&6!qAq*@6d}`rqE8K&Ol|n-H(*jd0obucX#XnKT4xU4umY9~ zy_SpC{{w^|Yg(u;kq_|mEw+?RxrpEJ%vcT_I_@P1OlGw}w-S`Qx}r?1VpCTa~NoGh_u2yUg9V^BZkHWnVmY5RkVo zwGSoFlpkw~6>UH z&RP$Jfx&Yb__shkBZ$}v^SWKONMh+%O<;^2*6Sw3qJB`UUz8-c2rDh=r`O0Hvq-k0 zFZX=zj-EpY;#VV=TzhMOEeCu|q?$^+d)Kb6?Bgr7QKZXEQ#->6{c>c_UpOIdVSp`w z&lhKwi5n86J`Gb?m)-IvxJ*(4$)8%K4PFj9ui2)=_7eikqB>yeuFFXPQkUnq5ite{ zA{vso$2$9bQtX~roiIkv10|KxZZI{=3np6rW*hV^%%z*S(jOW5+hOjLbn6!a?Eh ze+=;C`X~RKye-dPcNvt=KM|n;mlb3O8(~cjPIa7K0zD(%gCMM69)*JLKCxKlqm=xa zAgw8~S(%a-rTrgvY0KT%@imRSbyQ4ldN?n6UH9E0!6vJj1Q5X)(l`Z>>q|Sg!OVR_ zU!WB*nf0I1Y|m;R#FvMU10z`msemAJKfsT=JcdjYtC)yonKN}QBy<$;JsfpowqEuW zC;UKB{^XdKuFm}@=<9?N1;Z`U4T;-CH7j)$2=3?!1I&9L&_u%_<^C`(iZ&N$MvCas zlwp+yEdIE@kOyc3W2=)tr=-0Lt~G7fUp#7Qsoc(8ym}mF;cDIdjK3fwFf?a^46%&& zFh#P@>Crq(Oj+Z5|Ma79zBt_?>k~T{sJ)#ggEH?DDxks*48Bp^<7#d3$Oob50l0-u zhjkVf+wT+{_Jbvv;a6^6m%r$E(b=M z@Rce(&f*sf4dcw$r*8S!aKV`*XOMRL`wu90@5cIPH_#q0QN=o?`J_d}Qt|yhS}RNz z4tgceaB>LEmPn#v{rVxYoe&5`2}eSYC|5io7v>%#|4>~Qe%jyj#f9o7TjG?8g?fxGiztnWXjdgTwXlbrB9)KsG;bLHOpk!V;Stzz`O2P4O;O zBkvVV!xOq*Mb|qzSVfOf!ui_8CiggBfK~@d(FL5<-5JY_pf4GCG%9ofov^ zRYy4t!e49T;W0!HR|6NN{-aS=l+^Uta8$d@C=#$3|3&NvO#6!=ab&PgZpFg2r?l&* zkUJ5knPGqHB~JsZS}L0Wf*;|XC5$4Nc6neambNkl`td_6$`-YeA1DQer*JDDp-4R& z7pXWf?i=tMhK>`NbQ$ua)Urjbz$PU)Xo|NYNn=rCRhw}E%7McY8q;ift=vL3)UmC% zw&AMX0XmZh%01h^LtA|4w_r@O33H3lLo?CrF!K0SWcrz}^o%|m|7MDA)q07F5OX!I zIhR7riy>b6{Qnoa6g&MgR2UkV zJ8$J$b#G}2%X#q}VAAaGbQ8b_H>%521+%-1YLsv8Zh2w!1jhHsAYhCkRcX4!36tqF zI}ddTKG%kTd*5v3M&MJYxbf@O`r)kzF7U!dY?3T@ppp@Rf2I{q!m2jNsRQXKoAH-ZxtS zF7KJ)l#<`GWdKlSl!Hzi%d%7}7Mi+=e>wi0oNy-lf2vNIIa9rf=7PW@TB3x^ zi06bn%~?)R1n92wK2dHo;l8q;_*%=?nZW)eeke?w!;A%O?e{6BfMhF|g%|N0+5upO z0K~bGgIv7_9Vp@{p#7mUJ_{>E76iW{Iy5Pp-Bl11VNUKK@sw~6rjT20JKB|3x(?)y?0Jb(O_Su*NYMs&!qC&!CR_3#s)RyIfEdI4G_YW!+ z^#VN5(ToUZ?_9)gm#q3Va8NAm#Zc3nS?&seioq5S|7!6YZ>W}Av%rq;mB^J?@7SdL;-3o&xmkdr9tx*c zPfr)`de+4z#%rrBSqjN}8oAu0VWzDCjMA41Y5~$)Ug+J)yWU*;hnQi5f%7t_8U~@| zy{F_I%Eg7P&Eom%a}p%5H6~U?UBpv`PeA5NM{N!5Hw&K#NuUc4cA~o-3nJnYoK{tM zY*TewSa5GhY;;|}%JkDDDXof&I>#6X9qeGYl7L-J;gWQAheSX;E~%xmT?xSiYkI1z zXXPAZLr9DJX}7Ck;c0EvMrvnOxcW;U!c>JD0QaU{GuGX9cY_j8*Fa1an3ctFuaggA zq&b*j(Z|OXO31;o6~r2*BIMTIY;G>(<3P~Cyw7Wyy@=}jZyNX;)_r7@jp7CFR5u^2 ziOz}B%=(vUM)ac?cJnF?wu%E6TvwYFBos9Q8%QvH&9c&5ZXCnL@nVG7`+e1*k`I*x z9ya3zgJm1;uC2dt%d?lI+K@8Rljkqm&HZ#t1RMDl4hqd)Vc_xW(&W*8E1fE1Q!r== zZssT&FGEuO8>aaj@rEJ&Ik+|;?I{;UQ~;81WallUS}p#bOj<4@m)utpMse|Te+toq zE7`KmcpT=7ePD@a(0xw)LRMJ&5-;BqgLISmpUEDj`Kel|77Q|-yJSkGV!N6F76Yq4 z36OdcHDiEB{qWVBT!Vo2(_WsMO3JlF@5eXh=NwDYYk{Z}<;|orml~Yxe{c(DhYZl? zMySYxX!n)_vtTVwPPv^3f9;0J*)SJ*0DOShQ1p{NhZRov6{YH(LzX4Xifor5u(~a) zZL&;SBE9rW!3$yg2}+KCWJKh;KLUAGAFg#ET|tFkrY@ooa? z$df1yWeZaZ!?uij3qXd8OYxXqQ5|_G=-k%IiRou@=X>MVPvC(QnZo(7gq!PV)!keK zimJ@4I(qGd^N$8u&2djC!o05x2%I@c6{f*D4eF9k1nt=+$S*64O$PZfZRGo()30hI z5%h$o%hGSSbNJkNY;7B0g$eoC{!o2+6K!dm=F%kzCq)}^JLHEq*Ue@SRI|tFo;)n> zro>95Kdd@$t$wTlHyI*}qiOT<9|uob`$Lq|V~3c@8X)aSA0?S9fH5;1BFMx#+-*EO zn_R-CB3z3tLkH<(>;)6=KpVoGR8(;sKeT}S!&MrG>(Xb1aU)RbegoK`MA~RSIOX>M zGc8~YN#{=fSf2E(x^SC|CsC?(Rd3|uO2YpP!_SZ6kcr>`jY~P#grJ?HaL00sjo3y6 zviB82?uJr~f&|)Oy+9Nj>jtXW476S~dEU?i0!FEIi5YPJ#4(4gZ~D8CCscnKd=T); zT#Vr==U*7^YdI()PXzDIMqz3ic|_oyIjQXf-k#cEz@%?b;6*Gf#2~jPZHfkG;2ZiZ z>}9>G)PCj%rgZP|m;W_k@3zt|PpO85K&1`x9c_wvMHai9|LHISbY=lQ!bkc)qR@egtNtF38$9UlU=CEYg1G- zf249VN}dw1@%!-?i3^XdzCT7gLJUtFVQ7r%=a@_tK~NjtTM>c9e9&YIZv25p&I!St zwGInAT!-KsvE?L_C_NNX8a^jyk0u-(S)y0Y?Q6j}!u*nLa$dXt^~FD}p6idlJLAsj5`mUbbsp zk9QdE0z^&&hfq@_Gm+;N?zeO<7nC6k{jybTVpfF3KMS`!3zAF?DV9bSfT`OS*lLAH z`>sB2yx7NJQA-^%O33+JZKK@*bJ_KeCE3CQ4OhIT+I`5TQuY+ukngCi#09X1lXLNX zszk&Hu9oo@j-n60QD|lMjcYD|C=P7ykk?zjwmIUxJa)8kd0Qg^atg5oLbSc$WmHu) z87i5%xdrh?Nq4~k53x7HJ^E4uglUIE`4R;v4zzgH8t;g;biO1H!~dc;#-vh77$y`U zUrBsa%j1yZlm9}~J4zJL`!}|*@!fb@(^c=HjOxE48>UHaVk77Z^PqXkl8kWtW7iDq z;~;4N~k-s#;ua#B%^%S9Bb zoy{1Aby5+!@1iNtEjpXGpW#{Qts_=HDnQ)@g=v-P#R>SBx}ML+tYMLFYqiI zqe+hk!|NVH8Ju?CwBz(gYkG{i>^D98jNO(kpf%YIv}aF)0MdLabxby1H*=VpA5m-; zGeHl4wWf+8IcG8gv~caP4I2^1zh1h-%}cT1mbWXL_hNPcr>BxpoC3C6u(dR zI`cCE^hLcGx=2ZwTZjjCC~J}z_7|%=>&U3i=Yq~Qyu*STFcU+d>b&6F9*XXpQ++WEPZbcb*1 z!Y^5#+{rK(m@^m;)%3avj^=|kvzM2V=1@C$Ev`=wjKneG|EHN2ye+imr_1TvmXU~g zU-j~qiaVD1cT6{tGLQnUdN%?DwDG`=1Bs@?8Jpg+UJ`FAksXWD55WEjw^J$&@6*>7 z-1H3tT)9@8UqA?F`k>59_9$!|6{AW139pVhvuY*?utfN!TMUj#R0|}%l126KGr-U% z{@ONuHt+sne77YZWsz7(v+#@`&EZ9F!(3r6lH}~sR7+-q4*u>UY`P5z-xBVj5KOk@ zbYPYG9v8hj#SQ@yt3JO&f;^q>RC6l?V6}}eHGB5pbPv6PCBM-X)F(f<=02QmY7<5S zOpCJ{T`W=6fXsWoXi{no4o2t&EkNY;Yk|;HP#5zeD5+@_(hNmK!ZGCOuID;FH#leW zCr3Ww|6g3{dT1wz=k#?7+4hIyfS9mkT-%eJ?ZN^jW9a+`m^ZSq+1>=eq`)L0`ZoYK z--SM${n4}@HYSE-m9{KkO+Rjq=k1I44==P8YEOR9aHGo$&L?tP3TwjcaH+Dw zOvk8Bz6;qKL%c_=@eGbbcKiHBmb81Dz+o9v5HZ-V5cUnH(C)bdheuscja%YpyqFKl zs&!+r{F`AQk#HtW|7zCMNQUYLjG#;XP(3Quc-K%^nRVvB>j=$_Sx6luo87Ok-hXKi z8_!Pzcqvb-%cF~kI4CC@?tM|z7^|dl{%IRd?mJo)NXvG)95W=NwGzZ95+YRaprDo<@biF-gUzk3q^GI2K)>=IaSgE|*Q z=L90b{x?4CX;@a%ugn+n(pG)hW>lIJAqmESRUhcrVZ)KTa8(wKrfVnHlRe^N9^s*; zP8wGv)kB#u`&MkTS{bigTIdvGl}#6!%!JwO-VN)~4ZIK?X7&(gJO-YVeWAdt0T6=h z%ygkc!hp81ogq0+CVKch8c-2Jq1*wYfO}^<1UBETN{;ho? z!T&SF&Y8$O^ESAks07VyUTpN1XW@b&s4>;qJ>aOLPb=B=jH`CAll8GdTU`I4O#Ab^ zv_zZ&C~h3r*nR0Hx;;za(`bT4{w_-hT$QE9uTI zaTdhW-$sE+OAED(*mjey-dR zH+8DS!JH9KZ6gu{?m?0@2X!kK@75duvW_4j+EuF{(29fL#;jAicUDgeO?Mf6t2frd zD$#{Q{w?JSG#*fxRZ0)w!0c)Q8vskHLIeLPx&$*im2aC1pIosL6uBHBk z>#(T|$kY3yW=eAuGK=&o>-(fm$aIfjL8BuyN{4Vw-Ff2;8Jys&{cH6V;exz&ov4 zutyXek^d4WDGz;+DREel6~>Pj+Exo*V1LRsujsD4-Tc)A3Z4+naEdMhH^<9jOi)ST z_$Y)#x$&gd0fxDP`|>^^(yj>S0%%X1q9;LvDt&am#UKx#l-%L;Lq{qOD+)>=TB>u* zNX98aq!T*fC^w3=n=$z#`PO5@8ks%roXp7AifY+wYj251fPrc|?xqiWXX})pk zZlV*)C`jxOHMgu1)!$e|*n*6K-^Cd*zSYXHl7t||Ioa5QR^6TroWs|pT6lULD7kc( zJ3`eeL0U?=b%y$504;5<1Oq`4Qvye=;p0oWErd}*>GdEMYeZzQJ4Nxq9CX$qs!d@4 z+wLX?5vN5#$7!#zvwc<^R9=vr%RbP9tyEZ)diBo}?PkI6jiFY#t&c)tiCoiRmVbEA zXdh{I#aGfnfs#B8y=1!f=W3DPdwBK+8B8K6w#PMd&~rzZNAhtb#dGHT~@2tS_ zK`MhH_9zqV{b&_)vbo0xI3xi06qySO+c*k9A3lrzD5!(hk?L%ZHPGsg_!UPn04ef{+4<8(lH`6kthjt!(kVnyXDi{d6J+x#4 zywQ-fuSu~a`5hyyIDtiFjtd_1n<}<�$!oG8BzG=voweIET<9d&h?-(VM{i(DT;h z@jAKcy8H3l{h3}UwhgfQr5&DT)}(a2SUkRh5AqIy1g|ans6n&| zN|%^h^x@Ix#OIM1{+99`?En@ebUvcyU>$9x&BzG}H{^XeIMZ;y;NKBLFdit>7tZsf zv`6+fjmo3`BPSLcG;t7ck$*dF0qc|6W$)nu)h_f9mRkEH)mP1q~IqHcLqW2W_;cE~DaiHryztXKFU8IIv!@wHDRvD$gr8w7{QOipA}6Y2AZ05fz0u4`j)lV3K1}`4 zXn=dmA~2s&9^d!`)K=D0ii561(!MASJNz`H=kLGs;wyp7m5TX(;24z&_`_{lsH@-^ zuZJFHRf=KC3b|ZlS8r*4rI*IJd{e6sx*Z(*kJuAZM5cGsvC ze|e7^0oUaoL^8Yem*__)GTxrY6_Ije^9^ziE-J+2itSFF(JS4iaDDAGa!>6RzJ&97 z>D(>tIeZEMVYv{d;;PR3NP3=scDz0r2h|oBdGVbXDk~P-$CdR5yAtS@1~3WQ;M#fj z{g9W^Te5pfQO)m&q=`126t8ylP@gsWJeveY05n?-H{Jz^x@g< zz2I*n28dJeTh7oRS1j5A!%uUz2o=EkW6m`fxil%L=I`fws}U_nVYF!;xmpFN(wcah z>tgWdG9bdtR^ck0X*;DBc+xw_sgM03AVtVA@9Uz9nj;TBJw<%x`1n>w=xsz0$A6!W zUv#z+tL6DA7=DrmZ8);D!t5V2vnnr?6=jN}b6v*#V^uN}+3&}jLh>1KUxd0IuVS6H zMlI%Nx?S_^yVjqGODH}gxn*9KI&32XD_sPZ#L0Y{^8%5BCJ)ikLCmpkJ_4K@*~qjE z;^VN^C4GQ+krpB!bJ*mpWRieSdaE5|hS zq`MHM!s1H{#&iM5WnA>8Do?nVt;0S5f~xi)e7kC9*f?_>P)VW}9M^4}t`z+PI1}CB z%lzC>wWH16tyyCnEA0Qi?otvX3~EDLW&BX_;O~U%Jrzx{(f(A6#U$X`e9CKzrx@xQ zXmV6Hxe9UtC<&->5pO&nVo|>D=~AeB@;IW59d4}@WWq*B$s)sfdSdaobMUcLKz`yT zX8N<=s+Y(n$ltpNm>tE@{JX<*E6+$dSY>R+=_l2V_wJ=yoeJTLdaLh8k^#RyhF zD7oK~?Rrnrj8hz(GB)uDE0A|TQYje&O4#jN>4UX(i&(JwOupf=I{eI@oegE#HV8!seSj9FcUps9gK|B(ddB{k6!sk|x>EQi z0*&b=1!ZCY+Rw3DF-)&9QentX5bq6co~cNa!1%7_v_;j2=W?@u> z4Co+h#v1Vvl(9d+{EBz#Q(XiP$7jq=*PFXZnxh@~pxUWn!MWq>5q(WIF7y2W0fUnX zOE6sa6;e*$fh6Pz-+)Chp~;umCo)*B`Z#TCK%%7z#si`2L73XUE}wbsX}i)EIdWIy z-HilPdCmjK9EO_>-8@UN4G)W}OyFJOp3!wrvcNxS!#7OvstNX^m3NK=3D-dn84Sv2 zOtri0_WTJrgm-Dw(biOCAsCiU^Jg0&<;pMeALud3H6^M^@RcAvd+I1oh1-G!iraCW z;T^~XgQBx?@C|G?Z9LpsOageYq1Hra9|OlVC0E8F2?9$2go^c1k)+4M3CJm0+rS6+ ztu_&P;XFaJiR&Te<_vfl0)feZ0x%p)#M>I}AZ$#L{c{7{@~f&-t=aa-1B62oQh092 zQg#m&^*hpXdiT?B>KJ_rHSH3$n+p&xf)vUc9G6}=T5sy;rpRs*_#P6klTC9Zqx{Xp&NB}Sb+sh(`?KX(@rIUT# zD>4PB9IijuzM0PnqY{$ojJO*elp0xjyN8T5OiMteDA=#gqRiz$IzZ8&>J>=AZy_`q zn6wqwcV2TFGV<@XCuwg3`Op^u=PbTt>f{tf!oK|@JxdCWhKT)6>&@^9$`DoWWSO=H zxX|RRT%jEXZT71KA&B%{??9DJ;Nwj{`!zZC;!KAyTggnXNI5HEu_E^XY5*PzQ{CxZ z8T;3ibbb_bb5yCH=;x#oM%}DiSMaJCrCvwTuODK*X?e`T!y)l~a?leqJMwO9>8`lh zSs1qixFs5=L6{W;vSs~+XB)#eY2>8IcU>TdnCfA4ls1DJ3DpJFdvJFEg=W|==jw8Q zei-cw@4zFgcc{670GnVGcY6Jn)Wn_JVe-Z#3$sH(L5vl~nQA8AZ({D~_zx}|D^=6E z2XIz~aZl;n#%Ce(PG#FYGzrN=f#BiGOl=Mp?j&U(IS!YOcsAgpZnz*s+BIVnYH0HC ztT98i*L2ws4X!9YgKLdw%Qnl8m1hgcvx6o(?g9)zk6C*Fiw_3|Fsf#T45k&9W}&_$ zo`UAv5#VQj!-LQB737~sj71y?Phm!Y7?rg)F!Ej>BwRTi%+|v9BDZE;9N>tsh-Tm) zgG1Eh_TpGSA@xUX`G53H`HfTu;T0;9+1nm+5eG|fDs!p{MhOKft z=1)YLDIy59q6u&c11NU<-`$|vySllV9GxDg??Sy^M_s08DOfnT>pPinmHBrzf|Lw(h;xLkd6|_wUR33?YqSwwc?(rao)lVy9+ud&?WF zX<_RoWcJb;VfbfJurXV@oHk9YDtQ&0160U4_>!C%#G+B&pc%jrh49NMTo{w!*VwWo zTw4C+455+^p%!V(*JUESOR^*&U$TjL=oQu92h=?Gt09-b%c2Ux4;cyES=%(Uu+07j zTKQMj*z!dgWcMMtl1KosOUxO*=ccuqwXF)#tm%UU)^PpB_wVmfsqH51FI-gtG}T!* zyPyD!H$RF%PA{kw^7M)W2+=1k?eeLaWZvj7uk-``xcBG>YiJ4V!sGyJ>Y0j-1dc%8)@1 zaj;-g-gmpt>!?F`QJ?b()#C?k2ZN=dtVZIo2h_5@n^b4VmO}nV5HHU|!B9U2Q{BSf3hz zT{NS;lNGlSXpP3SG}Q}tw+c$1k|cm0gU1)gUI&w-Z$i62Il`(PD@QRdtuE>_n}}AB zy94BI9p6#wE0{Y(;0|G0cY4DDae96$l_-K3M;!Vh#K#D4*RaA}1Ne9CIc66_ev*t1 z4ZVMyR`5i<#Ek9OdMtiZ8?<6UU@uERvrs-fKJZZ#$1)Tk0+gmls)ye9Vp^w#H?oxD zw17td)+KMjMi$lp-_VL%t~roMhN|H(nve6dUL;s{`)x+eO7=RrVWb)a(Egpy-uZmP zC;4dmznd(&T6!&ZmZEEHR2i8?@5gQ;?r<5M-{o#`rG|~$D0~r4tGf*5Q%&wYx={q) zw?Cc_AixZ~dOy)Tf8KHY5VHow5f&g#q?sfn;Q_@I&Rn8jZQLe+gW8_ z>9r2SaFjWpQBiJA(LvW25~ju=0!uwXZfdgtRJpQV*iau1huGY$G|J!CCIu1*@BR`X z5slWxl_8c7A|N}@wq~V=l_k9+$Z;EoWjK`v-dF+?v}^gCwCK-{%FH_G|^m@OZ>-cX+71>+W%g1GejR9PA~OQf{cA1$4bh*1O*D^GMRT1(y{ zd$aN8mm2Zfr+~Cxp&1mC2dXLp3wPxDq$0IwrWKS8OtYZV1EMMamxjzIi7EL^wz!AG zgP5F#*u7y3psRymYeM;CIA z%QUgD%4nQjNv!vrLOj_Cb7Ih=w(0*Fn-ZZ8WI>lPSox;fA)lGPS7ct5abGwDj6PZJ zxHD4y9-zvVEga4MG~Rr23mnI$%gU#u(2kG<&V?dGVytz_-LYQi5UV(lP!?0jV}fT@ zZT6R7-izV^$L+yPH+tQw`HlzcZGX*n#y2IlyVlS%MP7Pq^Jl3W!c;iSl2jgM+=Vc^ zQNK&K84u&b4DJLf)v7(Nm*%XG6 z?ZI#i+q@}09cg(7e1_*{M#Sn4O{bIFoqEZ4<*+9EsrLO7Cq*7;0o(@LSn~Ss(m4tW z!p`;tZkZTRkpM3dkBVIboVAakom0`CAxN<|P+*_p~K$vI@j95eeZ40Zs-A!x%lHgBu^h*7ggseO$Ce$uR#6LDfaW ztxB{s4AWo^V{D^Q*e3~Q?OCAd_Z5ZpVPp{-CV+CR9ze5gIZ~t`j4xBmkrg_(A*45- zp9#xBH_sJ&%CbS0HFNg)(|N@mRG971*7;HcPHQn>x=I+pPx zP5Z4Wj}h=~;1;gc85I%bp5(0KJ&DV{EX;Ekc(%wG1cM&lPduWb(O3!A_dq4P;1h!p ztMAkNcPkrjFudCtvWcHxnK25IB8Mwd*ZXHmDu-h20VhF>3XN7b8N`@7k!x(XB-7ypRyO0 zX)FpAYqPEGtdNA}ycBo9RhSU|4d>OoQClqeX9?U>2};Y(hNHB=@2EL zjR}3f1Fo|PlwIp5yqPAFx->hnO1Z>PO}zUX9$uHd+I&1&7eBNU-O!RzYtA!Lt#kcS z+pc3(R#^ei1s`dQ5<`e8mTp%GbCMQXuREwVKI_qo-R`Vka;Mj!r3icF#SK%Nv(-8omfqQrV}m z-mhdZ>D`G}y!*rm#b(bTbKpvM7G`8TJ(qhYr{|AjAmD6nFV=`@6F1`np}N2)S87-@ zLT1d!=|Fpa^aaMSEm7_!prZyz!zVT#n<$#0lzDd1^SnJ^=C+i0oL%q904^N0I{7fm z(I0LQe5)5?V!z{3x_YUJ)TwBAA2CPe^^S`5pF8dYG6V${_F>QRgaySCXPQ_5zGNkZ zhO-#Jolxmea;NU11C2ij)Fb%dr~hY#D#Lw+tvD?0P>asRG)9Jf7%R~+%I4b_?9T~E z7;ye|6sy(PXp56+*bsjY3k@MNW8FNm+iU24)*{S@I69&`m^mf ztxi!yFzT=!-?!HsqRq&OP*Kb?Op+n$Cy8g7OBAmt*YgiJ918p)^TL&^1dIT>hEcyY z*7CWSc2Jsu%+FxKz5hgdC-&$LJu2%Zj@oHm_6WZN7P;O!>V36`#T|yj5^|`S|MP1a zcFBvn?{scQ;~N4{l)>y3ns8=z^%L91K79({v8c`nXf|?ORIei=*)svmW>4z5WO`bP zTTyo7G7sOZYRv=!&JEub|VcIcVK)x!(%P( zO{(2ZL_WeIhd4Gn_l9`g0X!$h%ROfxhmBQ9B>Bd@EGxvVy~;io`;OV}Ct`T$`VLPN zpxo6GhWy*ng{CSMdWnD#1fl8F)$APycU;ZdHeY&{=|v45HONXqRSQmtr~IUCISVo6 zhtnY?u=%IS+W^Qw3Nj!De7c6ur&1LBq$g%al5A$-`6YC6d>#`^Lr(D{v2wNmfF#_2 zv8OkF$}z_?gpbuxJWRxDmA ztVz7`IT)-EmM-L-hIF0ywNK{;0Oc+&_J$`rIX;vO~jmN~DRIhEje6zJVema;`^PoZ+)e zde-$V8_^m4=I-soc@0HU8$}Tib#@dZQR?Vm4ud?K^G)20-bXNALUBsdCALkg@@M2I zX8W58T!?~SGFrj4J{bdtmJR{&KMuf1X(l+u<;T*Emf#}?G=B9;9`*g?!&me2Y{|iE zTLqwUyl8`UnFEY|2gEaMeHT;+9iD3ZKfI{ov|p09weCH7{plf+bHjBCVmG}RO|1rt z5!dah%;K*wFuAb)mNkO?;hHy;&rQY0i731UIeXHF9~mnO#*((_)Kllx6h4TEHoSl8 zVg~Sq;_*sFA0+Vl4BHm&3ojKk@6r`RbBb*j4obd6s{EK!3Y(r#>0jVQQlPGY9X5)b9-QgynfdNTJ!nXFkNK4 z63d>$90%|sZnx?1u0(W{6ACGd&z1K40VyJ0ME>x3C{FrP5D-sgnhaE{IlF^`Y_U4b zqWgB6T^=gf)zdYYLMb$;7=+JT5*CY@qv)!ikZ;%LL)&yMcVcE2pyG=@ZoKA&2LhV7 z2@3E3|C}3F7_TSP9lDN%+hNg+mOM*LF+5)<6MoOW8CTe}+^f)+!rA@NSfYjACB1F~ zO)Bivq6BX$8;6$ZHu6nYF;1%vv+LO%QM_+vt02%8p3-ul{mEr=2Mhi$g&emg`#LeC z@Z4%=TpgeO>;g}YWLH&5$pVo+5pOzQrsy?g^mMLw$XgzM7Bm7i5VnZf;CSP~f*g(^ z7Xqkim|@YhE%WGV2%X%bRvg3z(7sv|U2z~gI=eT!?jEqL^}JbQ zDx23<1*LwJ+PoHK^kf@4mB&BdNh0|QLAcAsJSgo@bO7t|0%HA8o7-udj+~uwooN~h ze;X9?N?hGtaRiQ4>-laI6t=Jr%p=&r-TpQ{bT^czYz$_+(&lm!@9F;Py-_cO4t76c4oRm7(VhvnrnhFMQ8po$VQM$f9}CT%0%N1K z9c#>PgNb;T%HE19`iMZy{*5^!ZLSwx@C4W%*-04e9PZ$ch+v-^ZoM-Bc?{kENw4dQ zw>|X8S4TqLRdW!I5wk<3ICY{cG za+@l|4+&h)Zl_pjHJQ~!GMcFBzuLi-2P3Gf)6QMBkCG)D09STLEfUDJeop*2tuGa@ z7B_&hZoN$g&cu-L&BqAg5|_&$nyrZLstoWPD-)DS99OcGMwNjA#SEglw}?kN0g|}n zp8IcIA|BIe(H^(D$zu|rR2as;wi^lK^g`y84#7F1a5gXsFaMMy@r@?ib1jLVmLsnz zRia>=zp6YO5Kf}KCryH+@GRf2gm4w4L0W-$+KI#d(Iqj9OvNda+I02lf;Nygdk7 zyxA8%?Ha1Wik@AaE2qai@7uyH{)jj35UJ;whrafG9K9*d%m5_XP1b1JREu61i3wg) zP0A`48ZARM%R9?c1aP)_TiPg|BONIZ_BN!_A{fQiR}JHf3_ohF2AK8weX|5T+qJ0W zPj)Ib0-(OvyF?X+GcQ|D9&?K6+eOR0Km^09((YM{s58x_(d)sJrU1zeJ(BTU7DC1( zXppZ#RVQ^D6|mn^W24(;`cb8F;on*GX@5WX2<1ek2f6Qd*4|RF`evX=azoYMOtdS{ z73SXzrIf?5120e0yMbZj%Q`juH&FMnRn|u;DR%SWTeCfhy90*N5^j!7n??+Oz;u;aJ-j*I>d;-1BU8FWTR{0urxZM|8D*y6g}`E-B9iDoN2m zb&DY`s-?-!tuj_v2n4I#;YIyjPAsLqoC!<2`CC4T!M_j}*M~s!h^%IJ3Ef!=2^wlN z{Z+$!5#*eR&y}4PxK{MNwQ(Y|;o5U?B^vld&pnF!!H-1;zDWyqvLSQ{Y-Im6$VyO% zcUL(;7x>FG)1{5?97qBMm3S1j>hV~J&q&KV`LRIXqnY))8VsPL?A>U-d>`l(VJx9U z;RBZ^86^;=;cv<|cUG%F6jq9S*JC6$Vo>|WIBgjWvnQn9E?TdC2bz&p8t!!d5KP)8 zitq>8whV59V!p!#7j}?_8^IW5CZbV(xE-fG5XdxSQ`cBHnuVPm22H1|M3x-iJLT^0N1JS2jIY~uysgUh# zZt<)63fi1DUzBIB*;r{N(1#i_bJ9p1WF}GUY?PCd+y-A46*J65I1%6w0$>E3E9nuB z!qjAyFWVwYhuD8=Cr%R5BCIj@hHi!wnGJ{4U1u90M4*FkBb7n$D+1{|$(Z z&elYjeHDy(gPHq?hLco88bGLeuRI!wLc}M1&@3RLPt>u!q*0-FBDL_$61fgj9HV4u zNFCGvLQ8yRx)q8wYIbTd^UBD}Y{!d0pCl(E5tv=Cwv;&qicrv2{lT_zQ90arrp+Blb_4y>j=-Y-;rq+YydY{XhLH>4JsDuKGsI;);`W= zG2B-A2B;&?gio@KP=z$hdks3G1PYry;lP)pgz_XU$ryKuOWr3M-;TWkfJWVaoI(YU z9DI$Ry9prb{j%fR6cd=kQL|sND+5Kd_ICyFJ;9Lv4AQ2zz=o9-!w#r1>Ivd81U&U$ zX&1ozAIz5S9)(?g7m5{RU4$Kxh0^@_r=Zj;8NXSGy9N-34mnM zn_`sYq~5;WdaF=9VhoKG1j9DrCxocQyYp%Tmm#JwGEw{mnFA?_-f{c;kVJRW0P07Z zMhdJZ_gPO;JH3K46;4&8+q-(P;hs*>eoV z5r6t89Yofnv@}bN(GK2ng}42Z-lqJkp@*fLJexUl=+Y z0jF}^!~-zHo*)EIsD~zW4fE5`>5!m=e89vNr$H$CBj_&G3<~c31eu!jmdCypIbtJ= zY_Z&$;;UqW#a8-q3o?hh<0>Py{|$ts_?k~RKu$-U1X3H43e#Xxs>-7z4B^J%V9WN^ zl?Z87iRME=C@7zZpoGRDBq|BYjJ?*+A&g*a1<;Fv&iN* zob+aQ5f0|D-tpB1PnA_+M)}!i$oY7ebQ2mxMic~T+jF3I9J}H22OBo>qCt{%=$PH7 zv~VHqQZ$H7hHs`Ffbhi&5xMC0C%VBoW&`&WtbS;6R%LRNY?UJ>TL^b-KgprC1$YD; zPbGE~?Q@9r&2f$V`t)^J&ac4_i)X)BEr%s@5i>roOz!1N9vuHt5c7x01JQ0n**uyr z=qon0&RxjC;d z4Bep`slCltzd;&)SRYdJX?X>H?TN|gnJdj8E~F}50N74>yEP}5aBy_zoI|;Cj7F%m zNfqSLlNiuRh7DwX2*ghON72uL$nY9u?P?4jb_4}Xa_+1^d3dJovbZYOAODaWQp<- zLoo94xTh105t_=7vzsb-h#1TG#XBCmf%(b|P(@0iTvr5lv;I2|Bjtx*p2MaXrRJX2qgGp1NbTp*oY14Vdd(o%e^!2sla z1J0xTQViR7!shBboFC$xbt*g5qrec!^~aL`V2U&gA2*8tqewEjtKTQ?EK}hcn zQ`h0GYvPCICC@`S1R2LLJ@bd*9Q2hR3K{(Z^K7hCTx76~-x`-&Y}R!EcCr=k(nH0* z*-P3*4&-X5Qb!Cj;^V||@r<^Ixsu;p@02?ilo1KhLC_pf4vp_C$5-JV3B!?x5~6A~ z=uQyHpYc|vST})mQ~>3g1NK5_cT>*rod;CF-y{dcEoDo)bLm&+zJsWlMgX4=1B8|8 z*CTy>^;6DN_Db6{))7oCUtLv(*YlhjP~huZuBAn(jH5bZt~dfH?J%-L6e zmj{+J(!I17Z8T{}dzZMaZ008g1JNi1wVCn$zKJPGNl)sR&@#3$1Wb2u75M1qMZuk< zAx#he+EkmjK{lY?#Ng(~{GS???4tM&PEZjjI7KooAL~ov2~W75UAMb>UW0hYue!SX ztZ@{cboHfF*bI9*8LrrE6Vr5Tzv(oNCmK$8zG~no)BRHAwqtCumC6AN3U(0i$IcMT z@ON+$G$j98RNnO%OC=O7tZn%o-Be?;0#_OlZHSc6(HVSp$11ij+4YOP+!hPLobPatB5^rhrHl%QYtI``d{gd@%pDY162FnX|o-e{_%Iy_Amig zHMqAU9em#Jpb%4GRYs$LBSieXML8y0c#lrJU!MY(O$4I3ndn)Mh#}|)m4<=D5jR-% zqAu@X^)o0Su*coe5ZpZ8Cmkgnk3Ev4dcuaAZ^uNt&z3d zNMb^yi6qLC4M%ZI4MM4nKSt8~q{jyMR;+zK8Dp~*Tg zoKCe#s9X0qNLzb$YvL8khkk5x1kE`Rb2%;W=GZ{adZ%WwDw0eY*qy|8SZk3EYx8pf z4&yGaSCAyd1sAz$fyLR+tJbtP6|+UHOd~ABDE45GP7`< zI%Q@$CZz!Ida9c0v|!SEJp|nRmD$Q+1u_zB0%1mKY_nxl&MCfbpI`$i0mKeU#WE>i zy}F@y4!8u^><(khkfYNq_A`H&`5)y6&5%|#I&fVV+7-?)Af0u6xRegh3$ta|y1)em zTP+Y&eBFqY?k8uevM;>U0bkQK3BMiVWoGOdoS?reR_+I$KFd(0%7pK{Sv;y(2pMu5 zbG@Otoe8}ge#h42T}$Nc>6Oc=Vn9WF_6X~q2R$_iKTkm+G?P6mgh(yTU9ZJ)?=Vi# zR*wV%9m|At7HQuQL;PoWr^GkfLY1`;7`ULS?C4_9_z@#z4$3s77R01#TGK^cxbG1& zlbBIX9(LV6`F*IOY9yW;iaN2y7W=-ntW^U-kufthxmm%(8@kb$O$8l&mz8E-gs>kB z6%{;CyCR9_3bQ)sOXhb+4OeW9EC^;zEPQS>gDeIZ3|IYlW)}yl1PoPN-7U#7;1kf0^ z8Z(_T$&(`4%Gky#LKk5Or0|t3p~^ku7CKT{F`BPd6iY?p7kx;#<{B;BH?Za=sMzVu05E(DL1j?J?r5$s1d03XPS-DL~Xi)2(1-tXB`aU>Twa%|CO$_G{FVOuXwT9gMBzWelWtU;I4X*-y z^T2l#+ASzXZ&R*IQVIc_9M>6C=gF1`_Jsxn5eiSB<+{2YrWqROid(r`CqS4OE>KK@v7k_@mQOF?|-THv0 zh5Dw4UVo_%Dk7hJl6fvLgVe=R5jR``979JMhcsGy={9Apw-td=KlmY9dlXNW@B2Nm z9DN^%FLnEj*>1w6!!_+vbP?7R&J8`Pvl;7+t_TUEIY4?#ed zpv0VkB?<9wen1+Lol8_y@glLVOXF~g!aLhU+#i-4MEL9I5kV1@qy<^ga87JWG~t_3 z0SZL(V|dOa2_rvw8(T#0Ba9ew-YA>IoM0@g)!B#lt%;JbH*U(buOG<{mUp_+1pF}_ z36xiv2wgHPZn-DdNp*{fcgEpUd2Jwy=_MJU0K3XP4^4FmUhuUqjxosu>q}^Th6*>8 z2C*g$R#44k769K5;ePEUQ=cANOQH&S8@+wE9{u z>*Na~Nv&Rnc*(GP&a$09oZ90S6x(nVvy0qNkAbOH;ibeBdM7stfPE4Jq5Rkl^=8x&LZrt##dy8oSXEfA{0$oGI1@QAwUd7K6K1q5E6gZ4^Rx zHT*LVA(v`5(l8%CHC0m#ayw;^Qtyno&oRK-UE;>`t-|k-8k1)4HtfwM?M7onTK3uB zW()|6)cr?Sdc>WbUBCoJ8XND&ZWukkQNdRLMn0<<7PppT8*6$#euHG1i`r749iqLV zv)Ibvtm+Ot*zh1Ex?PGBja*HoQbDhg%R?*e79FHiKZA#M3#HzN53Vz2_FWa80$(AO zhzB+ogGgUI7m_WbCymmf^Qw1>u_fMb-^@N}?KgCA0F126i7m)K`yJ z0k}~q;l2wo-h5@()E>zAeBfb(c@YJS~l=j zk7wB;={mIc%!ygj5K<$qADnQSL*<;n$+8H@CO@TRfppQdZL7kDTYo!3)J)< zstWA`gGRQCmj}UvCZk~fNn1bLj1ggBd|?{p?UlJw73{A_#7#<0?RH!7mr0 zdPG{E@uSO%?91aoSOrPq3I@no1y9=t9xpG_(9zW;JKw%g96BN8N%x0paDONwn=Np7 z5M7&7#L4MAlu!*9rkD%}>T8Vxx6M&Wl@WMEyuiR64cF&1P&!8t7S!2*gRj!jl?}JQ zp`WExcJ{y5s~N=A8504XK4$Fd{S47pYL+M0Gh){mYT2eH$9Fy-TF=Of6L1Iz8xv_e zx*9^d$mm#LSNg?J>3p#OmB6^lc!8b`7wF3{T(F zYO`FRu9{D1wkn&^*3z^BcQux?>g$qdi?j2{M3&s&x;$qHO@MqujTHsun%(Hi3U1mjnos1ad2dO5ym zbe?_UIhtMp1-I52tGm*Z8~R5~90u7qZy=IcqD*Hy+|y?YH^MIEucd(a)I?qlMF~tF z9C%mw51O4ts3ffAuP|kv2o-NB%3oyiMdy7n57m+{5w!2HCv019! zxJ@3N?=CjV$uY>v5}6&Dr(X}YYEx!fJX-KPv0793n9ldcIeC}}usF9^7UpZ)2Th{S z8+1H){XD}4y3||V) zXppoGls;Dn38_(B+^d9u8K2km8-!tXra9SR)Nh*ue+B?_7gDBS7)gXK#h8ic@$7bw zZHkP9|3qu`$zQm?G2e+oZ z8$Qr2Mc&_{(&MM&fOj6RtZgQ`#1F|``Z_j(NZj`}3&>59_HOjd3d3YAUc*n+RTEq) z7alsvwHyncta&~~8D|7|&w_2O4eMO-ulH=Mou?_!N2d#RBeJVFGPI@K1M^dDg_ShH z8gxJ1JX>YMit%Mih@En)W9Y6%?!W0I43rK?1fb9v^$+p!N^+sh;(P11hBep;JcM8> z>eUZB8CU24x{BzY4V6hUYgj_8$?r)S$68cE#S#sMeXIFz7x77z-mbBKioO#!@$XA_ z5}(;eG{>tiCr%>12R<5d4g;VRExp;bt)angzGZwpC_QjL;(Mm0O--zE`1t;d24}2I zTAsQP2O!EHiUhbgS$Le+_+B^;I2WTYs%iz{8Fs%e!%k3`B&*R**gsbXi#-zaPXR<7 zHgsm1!(CRN3}dopPda6CE)m6y@Om|DOtAR;c)Y*QNy@VS_sd@*0x)CIs#||4=4J{h z+~|ZN@bnQ}y1zxj3IXYNidtu#1}}26g)x2~YN5hTJ6KxNVEDt>vCCm5DPQ}E*e9wI z46PSgw03}cM`*~zE5tc#}s1;ceMxD3Zr*k!g)!8<&ik~F439jK4Smt zP@YP75*;p^s>n!A0F5i?nNFD1mBGZNf(Pt0BxlaTs=xMK-rRg_|L~9JB!Xr|by1Vi zb05Fqy>8F71-#dNx&IJL`6liCD-OUs9>4yN6;mdT#ZLO_N&I0%FZNQTqJ7v8bY_+Y z&OYif7;^H<4`$Q|Y)8P*?7t#gj*2t3hNdcuzgF4R&^Ai57Y3!Db4riZ@|=-0WT+W_ zFTt{9Uvo7G81uae<2p&}1q(lzUCi|hOltW!H_V6_cP~MbfNvuK6md&QLtUI92(?WT z9nt#SjjcPa@tSazEQdMe4Mc%Q4*vb$2^;Il8Fs6gOvSnU*}s)-L0g4bq6D4Cy7c!7 zNf|8I=BnF0BklvUvOOZKKws9*G%%YQ)G`DHLy*^yXfLwWQ>}N`5q=t#kpjz%Q8&|i zoi*597Ug$O8Gz6ZX5cl46nu5A5-vPRC~sc6f7mexucUkdO02 zw|1=bZp}jMS&l*a89+*zzjKB9Js$v&?mYJq0l{PiPWI6Gx=Wl=hqjq81?u51aLB=r z_BcbH8lO&vmOTCuRaNVw4p5#jQ8DvZ19Z;|6wa;5ugUXmi>cGY)r~v>R{XiC91|FN zEyHqd9(g$pkcRZNYe1il9b)lCAv!tA;&tJdx58b2Y({l@8Pz%CM8gZo&D)lO%c)3B z@9Bz&2|c9XnoRXV!7Iu)9F^^bcM-O5yX9j?B4yLEr!g~&9>Ctk+6ns(P$&KW5_N^j z>gDj@j6}Bo?(ev`v(K>`;19tS)xh-BA<8yL4im!?$m7+98*q*r3`0qR?~&0Qo;+3Q z8bc^5t{Ad~Al&uBY}g!gC=hClOI|k`Y^9@J9?lkP?op!s{LhSFBnSH06vzfH68?Dj zy)Oy3+C~f%p5`E0**?2#Ksi6%5OrF~9(>mAfq5^2%zjrqUQN1M*5yg42j4D>rJDcy z2}Yj|U818nLQ%Yl3@euZi1C%Vgs-uoP9!E3mCG$4dZjK3M=QX%h7?5>nb#qcc1_aI>n&6@dFHr%|o& zX#&z<^iP+Rk2_N3zEcVIAg1zsCq^y)71n(qIca!+3**JHay`0v%+>Vlk`s1-jnb$6?Pf+F`#i>}LGhc7c8MoJyb-{U)9jBc8 zi*lsUmmg=pH3~Sht+a@R#S>Erb#Q-I+%NKo`6Rac}brZ(%$LVK;3vzX}>01LrKgY`kD1+Z_Pz8J^;?TSS! z`y_LJn3NS&K2gf*3o|)`F_?S4l`1dons#G0A|+CDAIi(_-u?WrScD|v9|Q;U?%&Er zdHrv{voS8)ox*r>*O~ywa9oE^dRxVM2JjSSR_Mg18#dwfO1|}>J(kAIR$A?eef8KU z3TfgB1RKJm;*9bU%}@NOAbG2IABn@SYLX-v>8Z3+zrUJV2jJ;p{eBQ}qdV&b*8#T) zvi)o|W}*7OLG~`EgZeF>mGS92(&8> z-t-GguY!$%^YM+|U1`&MjKJ2jcX2Kux_L@t0_!(*Pj?}fJNH#5;F+rwz4T*29!LX! zOl&UgNAcXc8LWs$SFc#!YjB$8A+|*X)S=V97_q%)Z#=RX#yT8)6(^dSZVs}hD1J+N z*VcY6O_xE$Zxa!}gFBBZ!F$NXB7u2_c!iX4+;)-@peMviNS&Hn1sA9CI?9UqNL;l+ z78|0b6p1r?$Qe&~$W;0Pt`Y-JjI)N4vetF(?& zUUHnH032;DTIFhw9g}%o0Q$pypM%({q{M z!yepE_`aNkd*F6k?>_*G44`gyl0Zi*IK4+6$v@5Lxk^d5xXhs+i}0?8jWqEQWwMoV>(y!o4|pYo=J%yPVX6^F59`(ErSvpC0E4hM z8v|V;v(UQ(j63N{+)X{37i5r#`*~^E-L(I_e2)67A0h1GDRx0W&#h-Q4CU}gLuQ!t zi>&A+;R0juUg1`J5wc_fWd!6cM~;~xePWV!>;`usj-*-huX@P(bR@F00Sn&s+h~P` zj5Hb_i)3C&l!%QdCqmcq0U>7=HNN1s8SWswWCv!J#%GYv83}4A$-QrJq}4!v95v(0 z9sw=$8IEGy6w3IL9{_{EkBojL@cCnh9*FEHCQ{*UVRReg2*Gj@IRo8`-i<~STxgE~ zzjiC-v0IcckH_T(f7QA^0trWUbDT_)@@u4Y4lJe<8uyk)&wggT>9@mMr{bh`7oozb zqHBeg2sYkL!V{;?7ECiE7Or53mYIE9O-|oSBT+!Kas;Jjp0R52?4K!;LDz#_J~~}0 zZ0wUsZbSm@3`lC8uV|nNf5m6lECr*xvn_>uUAF&ss`#WkVh!(S1EgjXTHi(fJ!@n~ z)Jzd_KzWi1KHEY}G&3mSXsC;>A2*PL%eXYb%X6OlQ!xZBFz5sjNLPZ^0{%^NmgySz0ZFusumX=& z7u4X9s2ZS^ViWW)$S4Z^%-pvks{lr)3j4?*jQWKS>mK@ML0+dAZWQ|p5y=DxZgov= z1Dbg%6#Gr!vdeD7jGceOSQa*r4p0_~=^7oYngfh6H_4s$8tN6SI+0H4&X`r^HH_}0 z1~xmd(m(45shzga44Wm$2@B;fos)SeO40a7Nr^EbROUCT6gkR=`i!mW*nna&0^ySe zd8Dem=v5;ie8Bj_zLDEbrzns;AH~Q@``Xqw5&YVe5#FJ;yt^e?ftJ(WR=iar|@L06%8vNf8_#!>j7-zIQ z`4buKOPHJ9Yr2&wX+N0a|A3oYXp8C_FN!2jLSvgtEcauC_yU0dxnbXwaGJR_0z zBy_Vr+J2iY8)g(Mc-j{RJ@e4JCO8kZTLT!S;}{G}6jsx)+hE4Ar&Paaw~3x6xS~@Z zaQw0fT>mZpDDQe+7p#b+>0Lw!Xo5O^;T(j~s; zORVrCy!%FdZ$#?*^Xi{(xPD7sKl_J<9yRd6nb0<=p$>LguM~z)XbLyzNIgKhZjh~N z4K6Y00&T;yd*z6*C%keOltCJwn|~v_D=rz|ry`Hk$xZNx5|S2$@PX3W#E8kL2Zp6g zuah30_I7I6et&2Y{fe>$4ye;yOG;W#$R5zALk$0}g{iB$2XN>YM?#*>NETk+BVOtI zhwOwmr&zDSp8*F9{{2C+?Kk&(Kc5S(*{lI;A;u(O;m+KwGzR2JUB~kpy#1mHA-Gux zu2zVZiSQQM6#t-qQ>C3-hmsB3nHO?Akl)=nc8S}U;VZMGea0Z?Bdvd8u#LPbM{%po z`F~v5*4;iA&-^*qEXdpBf1bUl5itNg;EqU1d<96fy)w)Q^Q6a1Pzcc_Z9G3)NB@F( z4ywBy`d}-co^V;gAO0YCU2Ig){uehn#lb1ros!#90T2WOyFtAsWUKlcPmF}xzlOVk z$W|!Kfsgu;x*$h|6)8fbtMt?@hqm}2sFwQvOTJjkoo)e6z`eZS?qaHp7CU}9LmMz#7#$AAXjYSOhi)} zR|9V;sp-Po5L=e)ux!yPX9I~%)Qtetm)ZgJ=HZpEfQ@HpW{uUY3rbf3AU>q9U_c>J zCcu66xs1n6%{rkZ)q3+utvB~Q7KeXr{V3CeThxBH6qA!3?wlTlBxWHwUy+0*@Xls< z844kKV07lr7&1+*BMOWB$}wMnT)0BTQE)FrN^?O$AfV#rROqAy3z}GvE%usu5N3>Z z&T^1^w;$DR2I`<;6Ri*lYwmK7*B2*nP|V=W1&=W0zXOENf>oqA{RHrO6#pKC+Ze?*xT4#5edt? zc# z&q01>7CW)%2q@Od=bBG)4{s}~bk#~g5oxsr{^ta4orG9}Smwz7gls~#3cJkk6fL$- z&Z}6KI$^p~pGP%U6iIV;K_gNjm4eo->zo<5)xa>;3`7(?SU#MD=HWPvVI&5 zf@vNT>~|RhWj!lcYBg7vv-rx2d>u z#-?O5Y2S>HA$8m0)mpGeuYy*3+D8Q$kWONNkg z04SODF&})K$aOx~?k8EQjrC6x=m$g@{bHs;WscsQ6l1AK5tP^+c!EmavBsZ9TNKa1 zqOE^3H#dxh;M5cX8&>NLQ?cS&bt}O>@1~gwF*OibG%mYiNQ8h#zMqJ(1kzRB5gW#D z+6{ImiSr#!9-p|2?0zJk7^8OUH0(Su7eLGw}I6T9H$_CvKgx*6JPo@e<@)2GbZ|Wo(Oah!fe|OgQF3YO*IqDWeFh z-dF2K!ww}LH1iHnSsj`(sB6Uwt0h3L=*mPni` z7Yfs%x61%tcc>bH`FKS;c%Q627fUAXarVRBM@|p!#}Y2QHeBCdQ(;1Q?sapjJ#E?i z9^KiiY`)La^4l#uezfL7yevGmaae8vPG*`PWfaz>8H3^57WaWd8rdeRc+dQy64@9zBZSL1l*$jee87p(*Y;=YQYxZwM*_$Pr#4>CmNNKaU4 zVpcUJnS_$R7l@1_lda{-o2ZgAF%IFU9DXAvfR#HW#UTThk3j2Q1?EsREz_e?*nu_P zw&|S@1h&%7pQdrob;S$^-|R-hY#bB4n&wvEYeMkS+bM2b5Qp`MxU3{_J?)Lw0_I;% zA3vC<^&y3;&k}=eCa9CT4*j_GOH(!n+34koECX3JZO6ua=1@LbcK*w(vKKI$*C|_9}GA3 z&X!HxBL*J6DD8dhbHEiY$#6XC7lbto!a&^xb+y}HZhxbP1C!!AuRqgxQuDRNwx`@1 zuhytTqbGVsv_#w^=pUzC1f!@2EzRWc%`S;ew9YyxqDTF?K#AN3?R?A;^LW0Q-N@fJHA=b%Tx69iG!`5 zo!|7FpEk=W5LUYT32Kv$?3K4!j~vK^LGK0|qEvJ;R%2!g!Wa!+vf*Nk zBwG~J#HIy)fcj};1Y=^rR0u(&93b1@1rN0oj9o)#6*EChN<(tHXYM{f5-g$+DfH$? zp;1QnB4daWH9qYK4AIF7YHHr0C>_!$++n}$URWq^Kkwc`0)PR`r<@}n7RZasGkpZH zAToIUY4?{EW zFpNwv5y7zTy7jD*L???hMwjpije`A!#m91hB(aP-?oCCV1iE6XJQf*)D$;AFR)(#; z_i{R#WPvRV`ai<)C``-~&FrM7(PWsYH3A{BM6?IkEw0Mwba zuA$A|e1iLm)Z&4njO0vfk8<2v2!Om%!9y);rKirow100{hK{VL2% zkJIEW7IW4jVx6#3*N?tpR!B~m_ZsIlL&%9wgD(e^D(Xb$4O&_bt-!`884gr;AOzpZt)eR zXe4DCmUUkeWtuaVTx%Dnq(G0#BjN&3PLOn3yM)tO?}%V*Q!`&53It44jp>BxjIXlbkDp5n}@v0x`rqmB`_SbHeXB*f=1+}l#5s8c;}o(!OorAb!G{QG@g>!>w8 z4537#6@y5e!WMvB((YIGRVaf+YK`X|68&@PG!O(5{MN~{2rq@NMb0tqBu0vRxkeV( zMWun-n=y4g*|6zCOiTfj6lY05FR-=Ac2xYhu7vsY_+sk0KUzbf{m8a@1sgc;xIsTBx9>IIE+a_5a#gA7T2Qr@*XE7?2vT*zC4%ly=}gw@=p% zvfJ?DYopms0$I-PW37Pn`3qwQcG{h1B-y4-reDF438au#{oH-{1&m*HIRi;xcMk^G zvNCh|VerV2DMU-S_4E}zKEiKjAci$$@^4K&n&^%yQqgcQ<0+C2UCXgKr!{D$p)~Zp z1gZ}SvTKg#3zK!nt&3Cx>-{OUGdl5!bc|eUXBF`R1V6M>Gp2}Mh#^YXoK06SahIHrF@2W(kBo7V6{8!ioV;KflLm6{>J;Y~A463MX)lQi9qc zYd7N?&lzEQxx}*B{U%@oUKCq7O2%?sAm=f?{V^+X5I{5|smn^+PFEuSXdnkJ-)gP5 z#j8tWAG9&wnHvm~g<9mX%7=D~OqgpyH)i3vgg(-*OA>Gw!>%KrC&GOSzq z2^E|^;{-)72z7>-xqK26L}MB)*5jG;SYr4~GdJO~OhM62F4yc68L02sHkN60ZcSo< zh0Kz>VjqIq?l6Tc$8|??q`@uoBDvLw`{DVWcC-?o{K@9r$n(NOWTJv^1Z;C)=x23g zBL2bM5Et_;kZwI$n3NedEGg!F9lUh)0>*Wwk=3B7B7-@}l>U~bkMYO_EaAEzb}c>N z9Q?A2H6QQfru|2e0~luD&Rl9|^dE~_yvlV>##*Tr(~WA7{xddC}5Tv zndZ|^@pnEVOC-wJ_On`QMki8=?_;if&@y?tC+_%=4=n;x7m4^PRinPm|&|63ewRccdh3q-y;mZkn+hIk8UkBV$E z9w*_R_s(H?K9c{IM%>KGDAl??o^Z@wnyEMY43)>}RCtl`V_3DIYPd}o PrwOY*-g1h^plsaB|2yQh literal 0 HcmV?d00001 diff --git a/packages/react-native/src/ReactNativeFileSystem.ts b/packages/react-native/src/ReactNativeFileSystem.ts index 0eaab55429..bf8e9fb353 100644 --- a/packages/react-native/src/ReactNativeFileSystem.ts +++ b/packages/react-native/src/ReactNativeFileSystem.ts @@ -1,6 +1,6 @@ -import type { FileSystem } from '@aries-framework/core' +import type { FileSystem, DownloadToFileOptions } from '@aries-framework/core' -import { getDirFromFilePath } from '@aries-framework/core' +import { TypedArrayEncoder, AriesFrameworkError, getDirFromFilePath, Buffer } from '@aries-framework/core' import * as RNFS from 'react-native-fs' export class ReactNativeFileSystem implements FileSystem { @@ -36,7 +36,7 @@ export class ReactNativeFileSystem implements FileSystem { return RNFS.readFile(path, 'utf8') } - public async downloadToFile(url: string, path: string) { + public async downloadToFile(url: string, path: string, options?: DownloadToFileOptions) { // Make sure parent directories exist await RNFS.mkdir(getDirFromFilePath(path)) @@ -46,5 +46,21 @@ export class ReactNativeFileSystem implements FileSystem { }) await promise + + if (options?.verifyHash) { + // RNFS returns hash as HEX + const fileHash = await RNFS.hash(path, options.verifyHash.algorithm) + const fileHashBuffer = Buffer.from(fileHash, 'hex') + + // If hash doesn't match, remove file and throw error + if (fileHashBuffer.compare(options.verifyHash.hash) !== 0) { + await RNFS.unlink(path) + throw new AriesFrameworkError( + `Hash of downloaded file does not match expected hash. Expected: ${TypedArrayEncoder.toBase58( + options.verifyHash.hash + )}, Actual: ${TypedArrayEncoder.toBase58(fileHashBuffer)}` + ) + } + } } } From 6b2d0a147076dfbd56f014f4bbabb50a4394a673 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 10 Feb 2023 16:47:43 +0100 Subject: [PATCH 05/22] utterly broken Signed-off-by: Timo Glastra --- packages/anoncreds/src/AnonCredsApi.ts | 7 +- packages/anoncreds/src/AnonCredsApiOptions.ts | 4 + .../src/formats/AnonCredsProofFormat.ts | 1 + .../src/formats/LegacyIndyCredentialFormat.ts | 2 +- packages/anoncreds/src/formats/index.ts | 7 + packages/anoncreds/src/index.ts | 5 +- .../credentials}/v1/V1CredentialProtocol.ts | 156 ++-- .../V1CredentialProtocolCred.test.ts | 2 +- .../V1CredentialProtocolProposeOffer.test.ts | 51 +- .../v1-connectionless-credentials.e2e.test.ts | 8 +- .../v1-credentials-auto-accept.e2e.test.ts | 0 .../v1/__tests__/v1-credentials.e2e.test.ts | 0 .../v1/handlers/V1CredentialAckHandler.ts | 2 +- .../V1CredentialProblemReportHandler.ts | 2 +- .../v1/handlers/V1IssueCredentialHandler.ts | 0 .../v1/handlers/V1OfferCredentialHandler.ts | 15 +- .../v1/handlers/V1ProposeCredentialHandler.ts | 6 +- .../v1/handlers/V1RequestCredentialHandler.ts | 15 +- .../credentials}/v1/handlers/index.ts | 1 - .../src/protocols/credentials}/v1/index.ts | 0 .../v1/messages/V1CredentialAckMessage.ts | 5 +- .../v1/messages/V1CredentialPreview.ts | 15 +- .../V1CredentialProblemReportMessage.ts | 10 +- .../v1/messages/V1IssueCredentialMessage.ts | 14 +- .../v1/messages/V1OfferCredentialMessage.ts | 13 +- .../v1/messages/V1ProposeCredentialMessage.ts | 9 +- .../v1/messages/V1RequestCredentialMessage.ts | 11 +- .../credentials}/v1/messages/index.ts | 0 .../registry/CredentialDefinitionOptions.ts | 2 +- packages/anoncreds/tests/anoncreds.test.ts | 3 +- .../anoncreds/tests/legacyAnonCredsSetup.ts | 418 +++++++++++ packages/core/package.json | 1 - packages/core/src/agent/Agent.ts | 19 +- packages/core/src/agent/AgentConfig.ts | 14 - packages/core/src/agent/AgentDependencies.ts | 2 - packages/core/src/agent/AgentModules.ts | 8 - packages/core/src/agent/BaseAgent.ts | 4 - .../core/src/agent/__tests__/Agent.test.ts | 12 - .../src/agent/__tests__/AgentModules.test.ts | 8 - packages/core/src/index.ts | 5 +- .../modules/credentials/CredentialsModule.ts | 22 +- .../IndyCredentialFormatService.test.ts | 443 ----------- .../src/modules/credentials/formats/index.ts | 1 - .../formats/indy/IndyCredentialFormat.ts | 65 -- .../indy/IndyCredentialFormatService.ts | 594 --------------- .../formats/indy/IndyCredentialUtils.ts | 207 ------ .../__tests__/IndyCredentialUtils.test.ts | 224 ------ .../modules/credentials/formats/indy/index.ts | 3 - .../formats/indy/models/IndyCredPropose.ts | 81 -- .../formats/indy/models/IndyCredential.ts | 34 - .../formats/indy/models/IndyCredentialInfo.ts | 60 -- .../formats/indy/models/IndyCredentialView.ts | 24 - .../indy/models/IndyRevocationInterval.ts | 18 - .../__tests__/IndyCredentialView.test.ts | 24 - .../credentials/formats/indy/models/index.ts | 5 - .../src/modules/credentials/protocol/index.ts | 5 +- .../v2-connectionless-credentials.e2e.test.ts | 8 +- .../v2/__tests__/v2-credentials.e2e.test.ts | 39 +- ...ldproof.connectionless-credentials.test.ts | 8 +- .../repository/CredentialExchangeRecord.ts | 19 - .../CredentialExchangeRecord.test.ts | 42 -- .../dids/__tests__/dids-registrar.e2e.test.ts | 127 +--- .../dids/__tests__/dids-resolver.e2e.test.ts | 62 -- .../core/src/modules/dids/methods/index.ts | 1 - .../methods/sov/IndySdkSovDidRegistrar.ts | 246 ------- .../dids/methods/sov/IndySdkSovDidResolver.ts | 88 --- .../__tests__/IndySdkSovDidRegistrar.test.ts | 373 ---------- .../__tests__/IndySdkSovDidResolver.test.ts | 123 ---- .../src/modules/dids/methods/sov/index.ts | 2 - .../core/src/modules/dids/methods/sov/util.ts | 123 ---- packages/core/src/modules/indy/IndyModule.ts | 16 - .../modules/indy/__tests__/IndyModule.test.ts | 27 - packages/core/src/modules/indy/index.ts | 2 - .../AnonCredsCredentialDefinitionRecord.ts | 31 - ...AnonCredsCredentialDefinitionRepository.ts | 27 - .../indy/repository/AnonCredsSchemaRecord.ts | 43 -- .../repository/AnonCredsSchemaRepository.ts | 27 - .../indy/services/IndyHolderService.ts | 294 -------- .../indy/services/IndyIssuerService.ts | 166 ----- .../indy/services/IndyRevocationService.ts | 198 ----- .../indy/services/IndyUtilitiesService.ts | 88 --- .../indy/services/IndyVerifierService.ts | 85 --- .../services/__mocks__/IndyHolderService.ts | 20 - .../services/__mocks__/IndyIssuerService.ts | 25 - .../services/__mocks__/IndyVerifierService.ts | 1 - .../core/src/modules/indy/services/index.ts | 5 - packages/core/src/modules/ledger/IndyPool.ts | 209 ------ packages/core/src/modules/ledger/LedgerApi.ts | 217 ------ .../core/src/modules/ledger/LedgerModule.ts | 37 - .../src/modules/ledger/LedgerModuleConfig.ts | 43 -- .../ledger/__tests__/IndyPoolService.test.ts | 427 ----------- .../ledger/__tests__/LedgerApi.test.ts | 399 ---------- .../ledger/__tests__/LedgerModule.test.ts | 26 - .../ledger/__tests__/ledgerUtils.test.ts | 45 -- .../src/modules/ledger/error/LedgerError.ts | 7 - .../ledger/error/LedgerNotConfiguredError.ts | 7 - .../ledger/error/LedgerNotFoundError.ts | 7 - .../core/src/modules/ledger/error/index.ts | 3 - packages/core/src/modules/ledger/index.ts | 4 - .../core/src/modules/ledger/ledgerUtil.ts | 9 - .../ledger/services/IndyLedgerService.ts | 503 ------------- .../ledger/services/IndyPoolService.ts | 349 --------- .../core/src/modules/ledger/services/index.ts | 2 - .../core/src/modules/proofs/ProofsModule.ts | 22 +- .../errors/InvalidEncodedValueError.ts | 3 - .../errors/MissingIndyProofMessageError.ts | 3 - .../core/src/modules/proofs/formats/index.ts | 2 - .../proofs/formats/indy/IndyProofFormat.ts | 80 -- .../formats/indy/IndyProofFormatService.ts | 584 --------------- .../formats/indy/__tests__/groupKeys.ts | 31 - .../formats/indy/__tests__/util.test.ts | 493 ------------- .../indy/errors/InvalidEncodedValueError.ts | 3 - .../proofs/formats/indy/errors/index.ts | 1 - .../src/modules/proofs/formats/indy/index.ts | 2 - .../formats/indy/models/AttributeFilter.ts | 145 ---- .../formats/indy/models/PartialProof.ts | 24 - .../formats/indy/models/PredicateType.ts | 6 - .../formats/indy/models/ProofAttribute.ts | 22 - .../formats/indy/models/ProofAttributeInfo.ts | 42 -- .../formats/indy/models/ProofIdentifier.ts | 33 - .../formats/indy/models/ProofPredicateInfo.ts | 53 -- .../formats/indy/models/ProofRequest.ts | 94 --- .../formats/indy/models/RequestedAttribute.ts | 47 -- .../indy/models/RequestedCredentials.ts | 81 -- .../formats/indy/models/RequestedPredicate.ts | 42 -- .../formats/indy/models/RequestedProof.ts | 24 - .../indy/models/RetrievedCredentials.ts | 20 - .../models/__tests__/ProofRequest.test.ts | 79 -- .../proofs/formats/indy/models/index.ts | 9 - .../src/modules/proofs/formats/indy/util.ts | 252 ------- .../sortRequestedCredentials.test.ts | 48 -- .../indy/util/sortRequestedCredentials.ts | 33 - .../v1-connectionless-proofs.e2e.test.ts | 8 +- .../protocol/v2/ProofFormatCoordinator.ts | 1 - .../v2-indy-connectionless-proofs.e2e.test.ts | 8 +- .../routing/__tests__/mediation.test.ts | 6 +- .../modules/routing/__tests__/pickup.test.ts | 2 - packages/core/src/plugins/Module.ts | 2 + .../core/src/storage/IndyStorageService.ts | 323 -------- .../__tests__/IndyStorageService.test.ts | 302 -------- packages/core/src/types.ts | 15 - packages/core/src/utils/__tests__/did.test.ts | 188 ----- .../utils/__tests__/indyIdentifiers.test.ts | 62 -- packages/core/src/utils/did.ts | 154 ---- packages/core/src/utils/index.ts | 2 - packages/core/src/utils/indyIdentifiers.ts | 53 -- packages/core/src/utils/indyProofRequest.ts | 32 - packages/core/src/wallet/IndyWallet.test.ts | 125 ---- packages/core/src/wallet/IndyWallet.ts | 690 ------------------ packages/core/src/wallet/index.ts | 1 - packages/core/tests/helpers.ts | 469 +----------- packages/core/tests/ledger.test.ts | 170 ----- packages/core/tests/migration.test.ts | 2 +- packages/core/tests/oob.test.ts | 4 +- packages/indy-sdk/src/IndySdkModule.ts | 13 +- packages/indy-sdk/src/IndySdkModuleConfig.ts | 25 + .../services/IndySdkAnonCredsRegistry.ts | 2 +- .../src/dids/IndySdkSovDidRegistrar.ts | 43 +- .../src/dids/IndySdkSovDidResolver.ts | 27 +- .../__tests__/IndySdkSovDidRegistrar.test.ts | 375 ++++++++++ .../__tests__/IndySdkSovDidResolver.test.ts | 128 ++++ .../didSovR1xKJw17sUoXhejEpugMYJ.json | 0 .../didSovWJz9mHyW9BZksioQnRsrAo.json | 0 packages/indy-sdk/src/ledger/IndySdkPool.ts | 20 +- .../indy-sdk/src/ledger/IndySdkPoolService.ts | 53 +- .../__tests__/IndySdkPoolService.test.ts | 113 ++- .../src}/ledger/__tests__/didResponses.ts | 6 +- .../__tests__/IndySdkStorageService.test.ts | 38 +- .../tests/sov-did-registrar.e2e.test.ts | 131 ++++ .../tests/sov-did-resolver.e2e.test.ts | 93 +++ packages/node/src/index.ts | 2 - packages/react-native/src/index.ts | 5 - tests/InMemoryStorageService.ts | 4 +- .../e2e-askar-indy-sdk-wallet-subject.test.ts | 59 +- tests/e2e-http.test.ts | 45 +- tests/e2e-subject.test.ts | 45 +- tests/e2e-test.ts | 76 +- tests/e2e-ws-pickup-v2.test.ts | 46 +- tests/e2e-ws.test.ts | 45 +- 179 files changed, 1813 insertions(+), 11755 deletions(-) create mode 100644 packages/anoncreds/src/formats/index.ts rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/V1CredentialProtocol.ts (91%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/__tests__/V1CredentialProtocolCred.test.ts (99%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts (87%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/__tests__/v1-connectionless-credentials.e2e.test.ts (97%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts (100%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/__tests__/v1-credentials.e2e.test.ts (100%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/handlers/V1CredentialAckHandler.ts (84%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/handlers/V1CredentialProblemReportHandler.ts (85%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/handlers/V1IssueCredentialHandler.ts (100%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/handlers/V1OfferCredentialHandler.ts (86%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/handlers/V1ProposeCredentialHandler.ts (86%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/handlers/V1RequestCredentialHandler.ts (86%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/handlers/index.ts (75%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/index.ts (100%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/messages/V1CredentialAckMessage.ts (75%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/messages/V1CredentialPreview.ts (80%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/messages/V1CredentialProblemReportMessage.ts (70%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/messages/V1IssueCredentialMessage.ts (75%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/messages/V1OfferCredentialMessage.ts (80%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/messages/V1ProposeCredentialMessage.ts (90%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/messages/V1RequestCredentialMessage.ts (80%) rename packages/{core/src/modules/credentials/protocol => anoncreds/src/protocols/credentials}/v1/messages/index.ts (100%) create mode 100644 packages/anoncreds/tests/legacyAnonCredsSetup.ts delete mode 100644 packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/index.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/models/IndyCredPropose.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/models/IndyCredential.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/models/IndyCredentialInfo.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/models/IndyCredentialView.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/models/IndyRevocationInterval.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/models/__tests__/IndyCredentialView.test.ts delete mode 100644 packages/core/src/modules/credentials/formats/indy/models/index.ts delete mode 100644 packages/core/src/modules/credentials/repository/__tests__/CredentialExchangeRecord.test.ts delete mode 100644 packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts delete mode 100644 packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts delete mode 100644 packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts delete mode 100644 packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts delete mode 100644 packages/core/src/modules/dids/methods/sov/index.ts delete mode 100644 packages/core/src/modules/dids/methods/sov/util.ts delete mode 100644 packages/core/src/modules/indy/IndyModule.ts delete mode 100644 packages/core/src/modules/indy/__tests__/IndyModule.test.ts delete mode 100644 packages/core/src/modules/indy/index.ts delete mode 100644 packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts delete mode 100644 packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts delete mode 100644 packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts delete mode 100644 packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts delete mode 100644 packages/core/src/modules/indy/services/IndyHolderService.ts delete mode 100644 packages/core/src/modules/indy/services/IndyIssuerService.ts delete mode 100644 packages/core/src/modules/indy/services/IndyRevocationService.ts delete mode 100644 packages/core/src/modules/indy/services/IndyUtilitiesService.ts delete mode 100644 packages/core/src/modules/indy/services/IndyVerifierService.ts delete mode 100644 packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts delete mode 100644 packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts delete mode 100644 packages/core/src/modules/indy/services/__mocks__/IndyVerifierService.ts delete mode 100644 packages/core/src/modules/indy/services/index.ts delete mode 100644 packages/core/src/modules/ledger/IndyPool.ts delete mode 100644 packages/core/src/modules/ledger/LedgerApi.ts delete mode 100644 packages/core/src/modules/ledger/LedgerModule.ts delete mode 100644 packages/core/src/modules/ledger/LedgerModuleConfig.ts delete mode 100644 packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts delete mode 100644 packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts delete mode 100644 packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts delete mode 100644 packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts delete mode 100644 packages/core/src/modules/ledger/error/LedgerError.ts delete mode 100644 packages/core/src/modules/ledger/error/LedgerNotConfiguredError.ts delete mode 100644 packages/core/src/modules/ledger/error/LedgerNotFoundError.ts delete mode 100644 packages/core/src/modules/ledger/error/index.ts delete mode 100644 packages/core/src/modules/ledger/index.ts delete mode 100644 packages/core/src/modules/ledger/ledgerUtil.ts delete mode 100644 packages/core/src/modules/ledger/services/IndyLedgerService.ts delete mode 100644 packages/core/src/modules/ledger/services/IndyPoolService.ts delete mode 100644 packages/core/src/modules/ledger/services/index.ts delete mode 100644 packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts delete mode 100644 packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/__tests__/groupKeys.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/errors/index.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/index.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/PartialProof.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/ProofAttribute.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/ProofIdentifier.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/RequestedProof.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/__tests__/ProofRequest.test.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/models/index.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/util.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/util/__tests__/sortRequestedCredentials.test.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/util/sortRequestedCredentials.ts delete mode 100644 packages/core/src/storage/IndyStorageService.ts delete mode 100644 packages/core/src/storage/__tests__/IndyStorageService.test.ts delete mode 100644 packages/core/src/utils/__tests__/did.test.ts delete mode 100644 packages/core/src/utils/__tests__/indyIdentifiers.test.ts delete mode 100644 packages/core/src/utils/indyIdentifiers.ts delete mode 100644 packages/core/src/utils/indyProofRequest.ts delete mode 100644 packages/core/src/wallet/IndyWallet.test.ts delete mode 100644 packages/core/src/wallet/IndyWallet.ts delete mode 100644 packages/core/tests/ledger.test.ts create mode 100644 packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts create mode 100644 packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts rename packages/{core/src/modules => indy-sdk/src}/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json (100%) rename packages/{core/src/modules => indy-sdk/src}/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json (100%) rename packages/{core/src/modules => indy-sdk/src}/ledger/__tests__/didResponses.ts (94%) create mode 100644 packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts create mode 100644 packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts diff --git a/packages/anoncreds/src/AnonCredsApi.ts b/packages/anoncreds/src/AnonCredsApi.ts index b52f4dbc0f..c19e537a8b 100644 --- a/packages/anoncreds/src/AnonCredsApi.ts +++ b/packages/anoncreds/src/AnonCredsApi.ts @@ -1,4 +1,7 @@ -import type { AnonCredsCreateLinkSecretOptions } from './AnonCredsApiOptions' +import type { + AnonCredsCreateLinkSecretOptions, + AnonCredsRegisterCredentialDefinitionOptions, +} from './AnonCredsApiOptions' import type { AnonCredsCredentialDefinition } from './models' import type { GetCredentialDefinitionReturn, @@ -207,7 +210,7 @@ export class AnonCredsApi { } public async registerCredentialDefinition(options: { - credentialDefinition: Omit + credentialDefinition: AnonCredsRegisterCredentialDefinitionOptions // TODO: options should support supportsRevocation at some points options: Extensible }): Promise { diff --git a/packages/anoncreds/src/AnonCredsApiOptions.ts b/packages/anoncreds/src/AnonCredsApiOptions.ts index 78a8e77728..860ea059df 100644 --- a/packages/anoncreds/src/AnonCredsApiOptions.ts +++ b/packages/anoncreds/src/AnonCredsApiOptions.ts @@ -1,4 +1,8 @@ +import type { AnonCredsCredentialDefinition } from './models' + export interface AnonCredsCreateLinkSecretOptions { linkSecretId?: string setAsDefault?: boolean } + +export type AnonCredsRegisterCredentialDefinitionOptions = Omit diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormat.ts b/packages/anoncreds/src/formats/AnonCredsProofFormat.ts index 2bfeb689dc..c3c8c5ef7b 100644 --- a/packages/anoncreds/src/formats/AnonCredsProofFormat.ts +++ b/packages/anoncreds/src/formats/AnonCredsProofFormat.ts @@ -43,6 +43,7 @@ export interface AnonCredsRequestProofFormat { name: string version: string nonRevoked?: AnonCredsNonRevokedInterval + // FIXME: inconsistency in casing requestedAttributes?: Record requestedPredicates?: Record } diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts index 78342fe833..f4a6f2a0d2 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormat.ts @@ -21,7 +21,7 @@ export type LegacyIndyCredentialProposalFormat = Omit< * * NOTE: This doesn't include the `issuerId` and `schemaIssuerId` properties that are present in the newer format. */ -type LegacyIndyProposeCredentialFormat = Omit +export type LegacyIndyProposeCredentialFormat = Omit export interface LegacyIndyCredentialRequest extends AnonCredsCredentialRequest { // prover_did is optional in AnonCreds credential request, but required in legacy format diff --git a/packages/anoncreds/src/formats/index.ts b/packages/anoncreds/src/formats/index.ts new file mode 100644 index 0000000000..ea6ed1054c --- /dev/null +++ b/packages/anoncreds/src/formats/index.ts @@ -0,0 +1,7 @@ +export * from './AnonCredsCredentialFormat' +export * from './LegacyIndyCredentialFormat' +export * from './LegacyIndyCredentialFormatService' + +export * from './AnonCredsProofFormat' +export * from './LegacyIndyProofFormat' +export * from './LegacyIndyProofFormatService' diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts index 9ef264f501..46edc1fbcd 100644 --- a/packages/anoncreds/src/index.ts +++ b/packages/anoncreds/src/index.ts @@ -2,8 +2,9 @@ export * from './models' export * from './services' export * from './error' export * from './repository' +export * from './formats' + export { AnonCredsModule } from './AnonCredsModule' export { AnonCredsModuleConfig, AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' export { AnonCredsApi } from './AnonCredsApi' -export { LegacyIndyCredentialFormatService } from './formats/LegacyIndyCredentialFormatService' -export { AnonCredsRegistryService } from './services/registry/AnonCredsRegistryService' +export * from './AnonCredsApiOptions' diff --git a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts similarity index 91% rename from packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts rename to packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts index 6ee07a7588..ba6b913ce5 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts @@ -1,78 +1,72 @@ -import type { AgentContext } from '../../../../agent' -import type { AgentMessage } from '../../../../agent/AgentMessage' -import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' -import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import type { DependencyManager } from '../../../../plugins' -import type { ProblemReportMessage } from '../../../problem-reports' -import type { GetCredentialFormatDataReturn } from '../../CredentialsApiOptions' -import type { CredentialFormatService, ExtractCredentialFormats, IndyCredentialFormat } from '../../formats' -import type { CredentialProtocol } from '../CredentialProtocol' +import type { LegacyIndyCredentialFormatService } from '../../../formats' import type { - AcceptCredentialOptions, - AcceptCredentialOfferOptions, - AcceptCredentialProposalOptions, - AcceptCredentialRequestOptions, - CreateCredentialOfferOptions, - CreateCredentialProblemReportOptions, - CreateCredentialProposalOptions, - CredentialProtocolMsgReturnType, - NegotiateCredentialOfferOptions, - NegotiateCredentialProposalOptions, -} from '../CredentialProtocolOptions' - -import { Protocol } from '../../../../agent/models/features' -import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../error' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' -import { JsonTransformer } from '../../../../utils' -import { isLinkedAttachment } from '../../../../utils/attachment' -import { uuid } from '../../../../utils/uuid' -import { AckStatus } from '../../../common' -import { ConnectionService } from '../../../connections/services' -import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' -import { CredentialProblemReportReason } from '../../errors' -import { IndyCredPropose } from '../../formats/indy/models' -import { AutoAcceptCredential } from '../../models/CredentialAutoAcceptType' -import { CredentialState } from '../../models/CredentialState' -import { CredentialExchangeRecord, CredentialRepository } from '../../repository' -import { composeAutoAccept } from '../../util/composeAutoAccept' -import { arePreviewAttributesEqual } from '../../util/previewAttributes' -import { BaseCredentialProtocol } from '../BaseCredentialProtocol' + AgentContext, + AgentMessage, + DependencyManager, + FeatureRegistry, + CredentialProtocolOptions, + InboundMessageContext, + ProblemReportMessage, + ExtractCredentialFormats, + CredentialProtocol, +} from '@aries-framework/core' + +import { + Protocol, + CredentialRepository, + AriesFrameworkError, + CredentialExchangeRecord, + CredentialState, + JsonTransformer, + ConnectionService, + Attachment, + AttachmentData, + AckStatus, + CredentialProblemReportReason, + CredentialsModuleConfig, + AutoAcceptCredential, +} from '@aries-framework/core' +import { BaseCredentialProtocol } from '@aries-framework/core/src/modules/credentials/protocol/BaseCredentialProtocol' +import { composeAutoAccept } from '@aries-framework/core/src/modules/credentials/util/composeAutoAccept' +import { arePreviewAttributesEqual } from '@aries-framework/core/src/modules/credentials/util/previewAttributes' +import { DidCommMessageRepository, DidCommMessageRole } from '@aries-framework/core/src/storage' +import { isLinkedAttachment } from '@aries-framework/core/src/utils/attachment' +import { uuid } from '@aries-framework/core/src/utils/uuid' + +import { AnonCredsCredentialProposal } from '../../../models/AnonCredsCredentialProposal' import { - V1CredentialAckHandler, - V1CredentialProblemReportHandler, - V1IssueCredentialHandler, - V1OfferCredentialHandler, V1ProposeCredentialHandler, + V1OfferCredentialHandler, V1RequestCredentialHandler, + V1IssueCredentialHandler, + V1CredentialAckHandler, + V1CredentialProblemReportHandler, } from './handlers' import { - INDY_CREDENTIAL_ATTACHMENT_ID, + V1CredentialPreview, + V1ProposeCredentialMessage, + V1OfferCredentialMessage, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, + V1RequestCredentialMessage, INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, + V1IssueCredentialMessage, + INDY_CREDENTIAL_ATTACHMENT_ID, V1CredentialAckMessage, V1CredentialProblemReportMessage, - V1IssueCredentialMessage, - V1OfferCredentialMessage, - V1ProposeCredentialMessage, - V1RequestCredentialMessage, } from './messages' -import { V1CredentialPreview } from './messages/V1CredentialPreview' - -type IndyCredentialFormatServiceLike = CredentialFormatService export interface V1CredentialProtocolConfig { // indyCredentialFormat must be a service that implements the `IndyCredentialFormat` interface, however it doesn't // have to be the IndyCredentialFormatService implementation per se. - indyCredentialFormat: IndyCredentialFormatServiceLike + indyCredentialFormat: LegacyIndyCredentialFormatService } export class V1CredentialProtocol - extends BaseCredentialProtocol<[IndyCredentialFormatServiceLike]> - implements CredentialProtocol<[IndyCredentialFormatServiceLike]> + extends BaseCredentialProtocol<[LegacyIndyCredentialFormatService]> + implements CredentialProtocol<[LegacyIndyCredentialFormatService]> { - private indyCredentialFormat: IndyCredentialFormatServiceLike + private indyCredentialFormat: LegacyIndyCredentialFormatService public constructor({ indyCredentialFormat }: V1CredentialProtocolConfig) { super() @@ -123,8 +117,8 @@ export class V1CredentialProtocol credentialFormats, comment, autoAcceptCredential, - }: CreateCredentialProposalOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.CreateCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { this.assertOnlyIndyFormat(credentialFormats) const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) @@ -155,7 +149,7 @@ export class V1CredentialProtocol }) // Transform the attachment into the attachment payload and use that to construct the v1 message - const indyCredentialProposal = JsonTransformer.fromJSON(attachment.getDataAsJson(), IndyCredPropose) + const indyCredentialProposal = JsonTransformer.fromJSON(attachment.getDataAsJson(), AnonCredsCredentialProposal) const credentialProposal = previewAttributes ? new V1CredentialPreview({ @@ -291,8 +285,8 @@ export class V1CredentialProtocol credentialFormats, comment, autoAcceptCredential, - }: AcceptCredentialProposalOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.AcceptCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) @@ -364,8 +358,8 @@ export class V1CredentialProtocol credentialRecord, comment, autoAcceptCredential, - }: NegotiateCredentialProposalOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.NegotiateCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) @@ -421,8 +415,8 @@ export class V1CredentialProtocol autoAcceptCredential, comment, connectionRecord, - }: CreateCredentialOfferOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.CreateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert this.assertOnlyIndyFormat(credentialFormats) @@ -587,8 +581,8 @@ export class V1CredentialProtocol credentialFormats, comment, autoAcceptCredential, - }: AcceptCredentialOfferOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.AcceptCredentialOfferOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credential credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) @@ -654,8 +648,8 @@ export class V1CredentialProtocol credentialRecord, autoAcceptCredential, comment, - }: NegotiateCredentialOfferOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.NegotiateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) @@ -683,7 +677,7 @@ export class V1CredentialProtocol }) // Transform the attachment into the attachment payload and use that to construct the v1 message - const indyCredentialProposal = JsonTransformer.fromJSON(attachment.getDataAsJson(), IndyCredPropose) + const indyCredentialProposal = JsonTransformer.fromJSON(attachment.getDataAsJson(), AnonCredsCredentialProposal) const credentialProposal = previewAttributes ? new V1CredentialPreview({ @@ -719,7 +713,9 @@ export class V1CredentialProtocol * Starting from a request is not supported in v1 of the issue credential protocol * because indy doesn't allow to start from a request */ - public async createRequest(): Promise> { + public async createRequest(): Promise< + CredentialProtocolOptions.CredentialProtocolMsgReturnType + > { throw new AriesFrameworkError('Starting from a request is not supported for v1 issue credential protocol') } @@ -808,8 +804,8 @@ export class V1CredentialProtocol credentialFormats, comment, autoAcceptCredential, - }: AcceptCredentialRequestOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.AcceptCredentialRequestOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestReceived) @@ -943,8 +939,8 @@ export class V1CredentialProtocol */ public async acceptCredential( agentContext: AgentContext, - { credentialRecord }: AcceptCredentialOptions - ): Promise> { + { credentialRecord }: CredentialProtocolOptions.AcceptCredentialOptions + ): Promise> { credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.CredentialReceived) @@ -1017,8 +1013,8 @@ export class V1CredentialProtocol */ public async createProblemReport( agentContext: AgentContext, - { credentialRecord, description }: CreateCredentialProblemReportOptions - ): Promise> { + { credentialRecord, description }: CredentialProtocolOptions.CreateCredentialProblemReportOptions + ): Promise> { const message = new V1CredentialProblemReportMessage({ description: { en: description, @@ -1221,7 +1217,11 @@ export class V1CredentialProtocol public async getFormatData( agentContext: AgentContext, credentialExchangeId: string - ): Promise>> { + ): Promise< + CredentialProtocolOptions.GetCredentialFormatDataReturn< + ExtractCredentialFormats<[LegacyIndyCredentialFormatService]> + > + > { // TODO: we could looking at fetching all record using a single query and then filtering based on the type of the message. const [proposalMessage, offerMessage, requestMessage, credentialMessage] = await Promise.all([ this.findProposalMessage(agentContext, credentialExchangeId), @@ -1265,7 +1265,7 @@ export class V1CredentialProtocol } private rfc0592ProposalFromV1ProposeMessage(proposalMessage: V1ProposeCredentialMessage) { - const indyCredentialProposal = new IndyCredPropose({ + const indyCredentialProposal = new AnonCredsCredentialProposal({ credentialDefinitionId: proposalMessage.credentialDefinitionId, schemaId: proposalMessage.schemaId, issuerDid: proposalMessage.issuerDid, diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts similarity index 99% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts index d563555bd5..0c56bb46f3 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts @@ -3,7 +3,7 @@ import type { AgentConfig } from '../../../../../agent/AgentConfig' import type { GetAgentMessageOptions } from '../../../../../storage/didcomm/DidCommMessageRepository' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' import type { IndyCredentialViewMetadata } from '../../../formats/indy/models' -import type { CredentialPreviewAttribute } from '../../../models' +import type { CredentialPreviewAttribute } from '../../../../models' import type { CustomCredentialTags } from '../../../repository/CredentialExchangeRecord' import { Subject } from 'rxjs' diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts similarity index 87% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts index d1c27861b2..244489b95d 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts @@ -1,29 +1,28 @@ -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateCredentialOfferOptions, CreateCredentialProposalOptions } from '../../CredentialProtocolOptions' - +import { + CredentialRepository, + DidCommMessageRepository, + RoutingService, + ConnectionService, + Dispatcher, + DidExchangeState, + Attachment, + AttachmentData, + CredentialState, + CredentialFormatSpec, + CredentialExchangeRecord, + CredentialStateChangedEvent, + CredentialEventTypes, + CreateCredentialOfferOptions, + JsonTransformer, + InboundMessageContext, + EventEmitter, +} from '@aries-framework/core' +import { CredentialProtocolOptions } from '@aries-framework/core' +import { CreateCredentialProposalOptions } from '@aries-framework/core/src/modules/credentials/protocol/CredentialProtocolOptions' +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '@aries-framework/core/tests/helpers' import { Subject } from 'rxjs' - -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' -import { Dispatcher } from '../../../../../agent/Dispatcher' -import { EventEmitter } from '../../../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' -import { DidCommMessageRepository } from '../../../../../storage' -import { JsonTransformer } from '../../../../../utils' -import { DidExchangeState } from '../../../../connections' -import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { IndyLedgerService } from '../../../../ledger/services' -import { RoutingService } from '../../../../routing/services/RoutingService' -import { CredentialEventTypes } from '../../../CredentialEvents' -import { schema, credDef } from '../../../__tests__/fixtures' -import { IndyCredentialFormatService } from '../../../formats' -import { CredentialFormatSpec } from '../../../models' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { CredentialRepository } from '../../../repository/CredentialRepository' +import { V1CredentialPreview, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' import { V1CredentialProtocol } from '../V1CredentialProtocol' -import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' -import { V1CredentialPreview } from '../messages/V1CredentialPreview' // Mock classes jest.mock('../../../repository/CredentialRepository') @@ -36,8 +35,6 @@ jest.mock('../../../../../agent/Dispatcher') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock @@ -46,8 +43,6 @@ const DispatcherMock = Dispatcher as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() const routingService = new RoutingServiceMock() -const indyLedgerService = new IndyLedgerServiceMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts similarity index 97% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index a3fff6612e..dc0818a14b 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -6,7 +6,11 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { prepareForIssuance, waitForCredentialRecordSubject, getAgentOptions } from '../../../../../../tests/helpers' +import { + prepareForIndyIssuance, + waitForCredentialRecordSubject, + getAgentOptions, +} from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { CredentialEventTypes } from '../../../CredentialEvents' @@ -53,7 +57,7 @@ describe('V1 Connectionless Credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age']) + const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age']) credentialDefinitionId = definition.id faberReplay = new ReplaySubject() diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts similarity index 100% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts diff --git a/packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts similarity index 100% rename from packages/core/src/modules/credentials/protocol/v1/__tests__/v1-credentials.e2e.test.ts rename to packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts similarity index 84% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts index e34a95d2bb..0a79843374 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts @@ -1,7 +1,7 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1CredentialProtocol } from '../V1CredentialProtocol' import { V1CredentialAckMessage } from '../messages' +import { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' export class V1CredentialAckHandler implements MessageHandler { private credentialProtocol: V1CredentialProtocol diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts similarity index 85% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts index 06769cf1bb..cfdb8be704 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts @@ -1,7 +1,7 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1CredentialProtocol } from '../V1CredentialProtocol' import { V1CredentialProblemReportMessage } from '../messages' +import { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' export class V1CredentialProblemReportHandler implements MessageHandler { private credentialProtocol: V1CredentialProtocol diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts similarity index 100% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts similarity index 86% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts index 5e7731b72f..fb5757587a 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts @@ -1,11 +1,14 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, CredentialExchangeRecord } from '@aries-framework/core' + +import { + OutboundMessageContext, + RoutingService, + DidCommMessageRepository, + DidCommMessageRole, + ServiceDecorator, +} from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' -import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' -import { RoutingService } from '../../../../routing/services/RoutingService' import { V1OfferCredentialMessage } from '../messages' export class V1OfferCredentialHandler implements MessageHandler { diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1ProposeCredentialHandler.ts similarity index 86% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1ProposeCredentialHandler.ts index 998d3940fc..d4fdbec98f 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1ProposeCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1ProposeCredentialHandler.ts @@ -1,8 +1,8 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { CredentialExchangeRecord, MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' + +import { OutboundMessageContext } from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' import { V1ProposeCredentialMessage } from '../messages' export class V1ProposeCredentialHandler implements MessageHandler { diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts similarity index 86% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts index 2b831566b2..f52df4abb0 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts @@ -1,10 +1,13 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import type { V1CredentialProtocol } from '../V1CredentialProtocol' - -import { OutboundMessageContext } from '../../../../../agent/models' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' +import { + CredentialExchangeRecord, + DidCommMessageRepository, + DidCommMessageRole, + MessageHandler, + MessageHandlerInboundMessage, + OutboundMessageContext, +} from '@aries-framework/core' import { V1RequestCredentialMessage } from '../messages' +import { V1CredentialProtocol } from '../V1CredentialProtocol' export class V1RequestCredentialHandler implements MessageHandler { private credentialProtocol: V1CredentialProtocol diff --git a/packages/core/src/modules/credentials/protocol/v1/handlers/index.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/index.ts similarity index 75% rename from packages/core/src/modules/credentials/protocol/v1/handlers/index.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/index.ts index dc0528f7c8..8566870084 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/index.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/index.ts @@ -4,4 +4,3 @@ export * from './V1OfferCredentialHandler' export * from './V1ProposeCredentialHandler' export * from './V1RequestCredentialHandler' export * from './V1CredentialProblemReportHandler' -export * from '../../revocation-notification/handlers/V1RevocationNotificationHandler' diff --git a/packages/core/src/modules/credentials/protocol/v1/index.ts b/packages/anoncreds/src/protocols/credentials/v1/index.ts similarity index 100% rename from packages/core/src/modules/credentials/protocol/v1/index.ts rename to packages/anoncreds/src/protocols/credentials/v1/index.ts diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts similarity index 75% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts index 857ea12bc0..9a90ce094d 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialAckMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts @@ -1,7 +1,4 @@ -import type { AckMessageOptions } from '../../../../common' - -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { AckMessage } from '../../../../common' +import { AckMessageOptions, AckMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export type V1CredentialAckMessageOptions = AckMessageOptions diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialPreview.ts similarity index 80% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialPreview.ts index da44d37618..a5e1344bb8 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialPreview.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialPreview.ts @@ -1,11 +1,14 @@ -import type { CredentialPreviewOptions } from '../../../models/CredentialPreviewAttribute' +import type { CredentialPreviewOptions } from '@aries-framework/core' +import { + CredentialPreviewAttribute, + IsValidMessageType, + parseMessageType, + JsonTransformer, + replaceLegacyDidSovPrefix, +} from '@aries-framework/core' import { Expose, Transform, Type } from 'class-transformer' -import { IsInstance, ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../../../utils/messageType' -import { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAttribute' +import { ValidateNested, IsInstance } from 'class-validator' /** * Credential preview inner message class. diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts similarity index 70% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts index 11accf67b4..7601303059 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1CredentialProblemReportMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts @@ -1,7 +1,9 @@ -import type { ProblemReportMessageOptions } from '../../../../problem-reports/messages/ProblemReportMessage' - -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { ProblemReportMessage } from '../../../../problem-reports/messages/ProblemReportMessage' +import { + ProblemReportMessageOptions, + ProblemReportMessage, + IsValidMessageType, + parseMessageType, +} from '@aries-framework/core' export type V1CredentialProblemReportMessageOptions = ProblemReportMessageOptions diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts similarity index 75% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts index e6979ded12..2ba01a802f 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1IssueCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts @@ -1,11 +1,7 @@ -import type { Cred } from 'indy-sdk' - +import { Attachment, AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' -import { IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { AgentMessage } from '../../../../../agent/AgentMessage' -import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { IsString, IsOptional, IsArray, ValidateNested, IsInstance } from 'class-validator' +import { AnonCredsCredential } from '../../../../models' export const INDY_CREDENTIAL_ATTACHMENT_ID = 'libindy-cred-0' @@ -45,11 +41,11 @@ export class V1IssueCredentialMessage extends AgentMessage { @IsInstance(Attachment, { each: true }) public credentialAttachments!: Attachment[] - public get indyCredential(): Cred | null { + public get indyCredential(): AnonCredsCredential | null { const attachment = this.credentialAttachments.find((attachment) => attachment.id === INDY_CREDENTIAL_ATTACHMENT_ID) // Extract credential from attachment - const credentialJson = attachment?.getDataAsJson() ?? null + const credentialJson = attachment?.getDataAsJson() ?? null return credentialJson } diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1OfferCredentialMessage.ts similarity index 80% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1OfferCredentialMessage.ts index 5e83b70348..50897bf194 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1OfferCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1OfferCredentialMessage.ts @@ -1,11 +1,8 @@ -import type { CredOffer } from 'indy-sdk' +import type { AnonCredsCredentialOffer } from '../../../../models' +import { Attachment, AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' -import { IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { AgentMessage } from '../../../../../agent/AgentMessage' -import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { IsString, IsOptional, ValidateNested, IsInstance, IsArray } from 'class-validator' import { V1CredentialPreview } from './V1CredentialPreview' @@ -60,11 +57,11 @@ export class V1OfferCredentialMessage extends AgentMessage { @IsInstance(Attachment, { each: true }) public offerAttachments!: Attachment[] - public get indyCredentialOffer(): CredOffer | null { + public get indyCredentialOffer(): AnonCredsCredentialOffer | null { const attachment = this.offerAttachments.find((attachment) => attachment.id === INDY_CREDENTIAL_OFFER_ATTACHMENT_ID) // Extract credential offer from attachment - const credentialOfferJson = attachment?.getDataAsJson() ?? null + const credentialOfferJson = attachment?.getDataAsJson() ?? null return credentialOfferJson } diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts similarity index 90% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts index 2772595d33..7f6aff1f35 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts @@ -1,12 +1,9 @@ -import type { Attachment } from '../../../../../decorators/attachment/Attachment' +import { AgentMessage, Attachment, IsValidMessageType, parseMessageType } from '@aries-framework/core' +// FIXME +import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '@aries-framework/core/src/utils' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' - -import { AgentMessage } from '../../../../../agent/AgentMessage' -import { indyDidRegex, schemaIdRegex, schemaVersionRegex, credDefIdRegex } from '../../../../../utils' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' - import { V1CredentialPreview } from './V1CredentialPreview' export interface V1ProposeCredentialMessageOptions { diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1RequestCredentialMessage.ts similarity index 80% rename from packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/V1RequestCredentialMessage.ts index e06498a3f1..2814e6ebbf 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1RequestCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1RequestCredentialMessage.ts @@ -1,12 +1,9 @@ -import type { CredReq } from 'indy-sdk' +import type { LegacyIndyCredentialRequest } from '../../../../formats' +import { Attachment, AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsArray, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { AgentMessage } from '../../../../../agent/AgentMessage' -import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' - export const INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID = 'libindy-cred-request-0' export interface V1RequestCredentialMessageOptions { @@ -45,12 +42,12 @@ export class V1RequestCredentialMessage extends AgentMessage { @IsInstance(Attachment, { each: true }) public requestAttachments!: Attachment[] - public get indyCredentialRequest(): CredReq | null { + public get indyCredentialRequest(): LegacyIndyCredentialRequest | null { const attachment = this.requestAttachments.find( (attachment) => attachment.id === INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID ) // Extract proof request from attachment - const credentialReqJson = attachment?.getDataAsJson() ?? null + const credentialReqJson = attachment?.getDataAsJson() ?? null return credentialReqJson } diff --git a/packages/core/src/modules/credentials/protocol/v1/messages/index.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/index.ts similarity index 100% rename from packages/core/src/modules/credentials/protocol/v1/messages/index.ts rename to packages/anoncreds/src/protocols/credentials/v1/messages/index.ts diff --git a/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts b/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts index 1bf5614720..815150c6c1 100644 --- a/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts +++ b/packages/anoncreds/src/services/registry/CredentialDefinitionOptions.ts @@ -26,7 +26,7 @@ export interface RegisterCredentialDefinitionReturnStateFailed extends AnonCreds export interface RegisterCredentialDefinitionReturnStateFinished extends AnonCredsOperationStateFinished { credentialDefinition: AnonCredsCredentialDefinition - credentialDefinitionId?: string + credentialDefinitionId: string } export interface RegisterCredentialDefinitionReturnState extends AnonCredsOperationState { diff --git a/packages/anoncreds/tests/anoncreds.test.ts b/packages/anoncreds/tests/anoncreds.test.ts index e7abd466c4..d535b9bc60 100644 --- a/packages/anoncreds/tests/anoncreds.test.ts +++ b/packages/anoncreds/tests/anoncreds.test.ts @@ -1,5 +1,6 @@ import { Agent, KeyDerivationMethod } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' +import indySdk from 'indy-sdk' import { IndySdkModule } from '../../indy-sdk/src/IndySdkModule' import { AnonCredsCredentialDefinitionRepository, AnonCredsModule, AnonCredsSchemaRepository } from '../src' @@ -79,7 +80,7 @@ const agent = new Agent({ }, modules: { indySdk: new IndySdkModule({ - indySdk: agentDependencies.indy, + indySdk, }), anoncreds: new AnonCredsModule({ registries: [ diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts new file mode 100644 index 0000000000..105153cd56 --- /dev/null +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -0,0 +1,418 @@ +import { + Agent, + AriesFrameworkError, + AutoAcceptCredential, + AutoAcceptProof, + BaseEvent, + CredentialEventTypes, + CredentialsModule, + CredentialState, + CredentialStateChangedEvent, + ProofEventTypes, + ProofsModule, + ProofState, + ProofStateChangedEvent, + V1CredentialProtocol, + V1ProofProtocol, + V2CredentialProtocol, + V2ProofProtocol, +} from '@aries-framework/core' +import { ReplaySubject, Subject } from 'rxjs' +import { SubjectInboundTransport, SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import { + genesisPath, + getAgentOptions, + makeConnection, + taaAcceptanceMechanism, + taaVersion, + waitForCredentialRecordSubject, + waitForProofExchangeRecordSubject, +} from '@aries-framework/core/tests/helpers' +import { + AnonCredsModule, + AnonCredsOfferCredentialFormat, + AnonCredsSchema, + RegisterCredentialDefinitionReturnStateFinished, + RegisterSchemaReturnStateFinished, +} from '../src' + +import { DidsModule } from '@aries-framework/core' +import { randomUUID } from 'crypto' +import indySdk from 'indy-sdk' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import testLogger from '@aries-framework/core/tests/logger' +import { + IndySdkAnonCredsRegistry, + IndySdkModule, + IndySdkSovDidRegistrar, + IndySdkSovDidResolver, +} from '../../indy-sdk/src' +import { + AnonCredsRegisterCredentialDefinitionOptions, + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, +} from '../src' + +import { AnonCredsRequestedAttribute, AnonCredsRequestedPredicate } from '../src' + +// Helper type to get the type of the agents (with the custom modules) for the credential tests +export type AnonCredsTestsAgent = Agent> + +export const getLegacyAnonCredsModules = ({ + autoAcceptCredentials, + autoAcceptProofs, +}: { autoAcceptCredentials?: AutoAcceptCredential; autoAcceptProofs?: AutoAcceptProof } = {}) => { + const indyCredentialFormat = new LegacyIndyCredentialFormatService() + const indyProofFormat = new LegacyIndyProofFormatService() + + // Register the credential and proof protocols + const modules = { + credentials: new CredentialsModule({ + autoAcceptCredentials, + credentialProtocols: [ + new V1CredentialProtocol({ indyCredentialFormat }), + new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormat], + }), + ], + }), + proofs: new ProofsModule({ + autoAcceptProofs, + proofProtocols: [ + new V1ProofProtocol({ indyProofFormat }), + new V2ProofProtocol({ + proofFormats: [indyProofFormat], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndySdkAnonCredsRegistry()], + }), + dids: new DidsModule({ + resolvers: [new IndySdkSovDidResolver()], + registrars: [new IndySdkSovDidRegistrar()], + }), + indySdk: new IndySdkModule({ + indySdk, + networks: [ + { + isProduction: false, + genesisPath, + indyNamespace: `pool:localtest`, + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, + }, + ], + }), + } as const + + return modules +} + +export async function presentLegacyAnonCredsProof({ + verifierAgent, + verifierReplay, + + holderAgent, + holderReplay, + + verifierHolderConnectionId, + + request: { attributes, predicates }, +}: { + holderAgent: AnonCredsTestsAgent + holderReplay: ReplaySubject + + verifierAgent: AnonCredsTestsAgent + verifierReplay: ReplaySubject + + verifierHolderConnectionId: string + request: { + attributes?: Record + predicates?: Record + } +}) { + let holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { + state: ProofState.RequestReceived, + }) + + let verifierProofExchangeRecord = await verifierAgent.proofs.requestProof({ + connectionId: verifierHolderConnectionId, + proofFormats: { + indy: { + name: 'Test Proof Request', + requestedAttributes: attributes, + requestedPredicates: predicates, + version: '1.0', + }, + }, + protocolVersion: 'v2', + }) + + let holderProofExchangeRecord = await holderProofExchangeRecordPromise + + const selectedCredentials = await holderAgent.proofs.selectCredentialsForRequest({ + proofRecordId: holderProofExchangeRecord.id, + }) + + const verifierProofExchangeRecordPromise = waitForProofExchangeRecordSubject(verifierReplay, { + threadId: holderProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) + + await holderAgent.proofs.acceptRequest({ + proofRecordId: holderProofExchangeRecord.id, + proofFormats: { indy: selectedCredentials.proofFormats.indy }, + }) + + verifierProofExchangeRecord = await verifierProofExchangeRecordPromise + + // assert presentation is valid + expect(verifierProofExchangeRecord.isVerified).toBe(true) + + holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { + threadId: holderProofExchangeRecord.threadId, + state: ProofState.Done, + }) + + verifierProofExchangeRecord = await verifierAgent.proofs.acceptPresentation({ + proofRecordId: verifierProofExchangeRecord.id, + }) + holderProofExchangeRecord = await holderProofExchangeRecordPromise + + return { + verifierProofExchangeRecord, + holderProofExchangeRecord, + } +} + +export async function issueLegacyAnonCredsCredential({ + issuerAgent, + issuerReplay, + + holderAgent, + holderReplay, + + issuerHolderConnectionId, + offer, +}: { + issuerAgent: AnonCredsTestsAgent + issuerReplay: Subject + + holderAgent: AnonCredsTestsAgent + holderReplay: Subject + + issuerHolderConnectionId: string + offer: AnonCredsOfferCredentialFormat +}) { + let issuerCredentialExchangeRecord = await issuerAgent.credentials.offerCredential({ + comment: 'some comment about credential', + connectionId: issuerHolderConnectionId, + protocolVersion: 'v1', + credentialFormats: { + indy: offer, + }, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + }) + + let holderCredentialExchangeRecord = await waitForCredentialRecordSubject(holderReplay, { + threadId: issuerCredentialExchangeRecord.threadId, + state: CredentialState.OfferReceived, + }) + + await holderAgent.credentials.acceptOffer({ + credentialRecordId: holderCredentialExchangeRecord.id, + autoAcceptCredential: AutoAcceptCredential.ContentApproved, + }) + + // Because we use auto-accept it can take a while to have the whole credential flow finished + // Both parties need to interact with the ledger and sign/verify the credential + holderCredentialExchangeRecord = await waitForCredentialRecordSubject(holderReplay, { + threadId: issuerCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + issuerCredentialExchangeRecord = await waitForCredentialRecordSubject(issuerReplay, { + threadId: issuerCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + return { + issuerCredentialExchangeRecord, + holderCredentialExchangeRecord, + } +} + +export async function setupAnonCredsTests({ + issuerName, + holderName, + verifierName, + autoAcceptCredentials, + autoAcceptProofs, +}: { + issuerName: string + holderName: string + verifierName: string + autoAcceptCredentials?: AutoAcceptCredential + autoAcceptProofs?: AutoAcceptProof +}) { + const issuerMessages = new Subject() + const holderMessages = new Subject() + const verifierMessages = new Subject() + const subjectMap = { + 'rxjs:issuer': issuerMessages, + 'rxjs:holder': holderMessages, + 'rxjs:verifier': verifierMessages, + } + + const modules = getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + + const issuerAgent = new Agent( + getAgentOptions( + issuerName, + { + endpoints: ['rxjs:issuer'], + }, + modules + ) + ) + issuerAgent.registerInboundTransport(new SubjectInboundTransport(issuerMessages)) + issuerAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await issuerAgent.initialize() + + const holderAgent = new Agent( + getAgentOptions( + holderName, + { + endpoints: ['rxjs:holder'], + }, + modules + ) + ) + holderAgent.registerInboundTransport(new SubjectInboundTransport(holderMessages)) + holderAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await holderAgent.initialize() + + const verifierAgent = new Agent( + getAgentOptions( + verifierName, + { + endpoints: ['rxjs:verifier'], + }, + modules + ) + ) + verifierAgent.registerInboundTransport(new SubjectInboundTransport(verifierMessages)) + verifierAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await verifierAgent.initialize() + + const { credentialDefinition } = await prepareForAnonCredsIssuance(issuerAgent, { + attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], + // TODO: replace with more dynamic / generic value We should create a did using the dids module + // and use that probably + issuerId: issuerAgent.publicDid?.did as string, + }) + + const [issuerHolderConnection, holderIssuerConnection] = await makeConnection(issuerAgent, holderAgent) + const [holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) + + const issuerReplay = new ReplaySubject() + const holderReplay = new ReplaySubject() + const verifierReplay = new ReplaySubject() + + issuerAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(issuerReplay) + holderAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(holderReplay) + verifierAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(verifierReplay) + + issuerAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(issuerReplay) + holderAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(holderReplay) + verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) + + return { + issuerAgent, + issuerReplay, + + holderAgent, + holderReplay, + + verifierAgent, + verifierReplay, + + credentialDefinitionId: credentialDefinition.credentialDefinitionId, + + issuerHolderConnectionId: issuerHolderConnection.id, + holderIssuerConnectionId: holderIssuerConnection.id, + holderVerifierConnectionId: holderVerifierConnection.id, + verifierHolderConnectionId: verifierHolderConnection.id, + } +} + +export async function prepareForAnonCredsIssuance( + agent: Agent, + { attributeNames, issuerId }: { attributeNames: string[]; issuerId: string } +) { + const schema = await registerSchema(agent, { + // TODO: update attrNames to attributeNames + attrNames: attributeNames, + name: `Schema ${randomUUID()}`, + version: '1.0', + issuerId, + }) + + const credentialDefinition = await registerCredentialDefinition(agent, { + schemaId: schema.schemaId, + issuerId, + tag: 'default', + }) + + return { + schema, + credentialDefinition, + } +} + +async function registerSchema( + agent: AnonCredsTestsAgent, + schema: AnonCredsSchema +): Promise { + const { schemaState, registrationMetadata } = await agent.modules.anoncreds.registerSchema({ + schema, + options: { + didIndyNamespace: 'local:test', + }, + }) + + testLogger.test(`created schema with id ${schemaState.schemaId}`, schema) + + if (schemaState.state !== 'finished') { + throw new AriesFrameworkError(`Schema not created: ${registrationMetadata.error}`) + } + + return schemaState +} + +async function registerCredentialDefinition( + agent: AnonCredsTestsAgent, + credentialDefinition: AnonCredsRegisterCredentialDefinitionOptions +): Promise { + const { registrationMetadata, credentialDefinitionState } = + await agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition, + options: { + didIndyNamespace: 'local:test', + }, + }) + + if (credentialDefinitionState.state !== 'finished') { + throw new AriesFrameworkError(`Credential definition not created: ${registrationMetadata.error}`) + } + + return credentialDefinitionState +} diff --git a/packages/core/package.json b/packages/core/package.json index 06b6aad06c..c990e6f480 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -30,7 +30,6 @@ "@stablelib/ed25519": "^1.0.2", "@stablelib/random": "^1.0.1", "@stablelib/sha256": "^1.0.1", - "@types/indy-sdk": "1.16.24", "@types/node-fetch": "^2.5.10", "@types/ws": "^7.4.6", "abort-controller": "^3.0.0", diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 3785d00f4a..84e282bf80 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -16,8 +16,6 @@ import { AriesFrameworkError } from '../error' import { DependencyManager } from '../plugins' import { DidCommMessageRepository, StorageUpdateService, StorageVersionRepository } from '../storage' import { InMemoryMessageRepository } from '../storage/InMemoryMessageRepository' -import { IndyStorageService } from '../storage/IndyStorageService' -import { IndyWallet } from '../wallet/IndyWallet' import { AgentConfig } from './AgentConfig' import { extendModulesWithDefaultModules } from './AgentModules' @@ -79,13 +77,17 @@ export class Agent extends BaseAge // Register possibly already defined services if (!dependencyManager.isRegistered(InjectionSymbols.Wallet)) { - dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndyWallet) + throw new AriesFrameworkError( + "Missing required dependency: 'Wallet'. You can register it using one of the provided modules such as the AskarModule or the IndySdkModule, or implement your own." + ) } if (!dependencyManager.isRegistered(InjectionSymbols.Logger)) { dependencyManager.registerInstance(InjectionSymbols.Logger, agentConfig.logger) } if (!dependencyManager.isRegistered(InjectionSymbols.StorageService)) { - dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndyStorageService) + throw new AriesFrameworkError( + "Missing required dependency: 'StorageService'. You can register it using one of the provided modules such as the AskarModule or the IndySdkModule, or implement your own." + ) } if (!dependencyManager.isRegistered(InjectionSymbols.MessageRepository)) { dependencyManager.registerSingleton(InjectionSymbols.MessageRepository, InMemoryMessageRepository) @@ -159,15 +161,6 @@ export class Agent extends BaseAge public async initialize() { await super.initialize() - // set the pools on the ledger. - this.ledger.setPools(this.ledger.config.indyLedgers) - // As long as value isn't false we will async connect to all genesis pools on startup - if (this.ledger.config.connectToIndyLedgersOnStartup) { - this.ledger.connectToPools().catch((error) => { - this.logger.warn('Error connecting to ledger, will try to reconnect when needed.', { error }) - }) - } - for (const transport of this.inboundTransports) { await transport.start(this) } diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index 28ad67488a..09c543bad7 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -31,13 +31,6 @@ export class AgentConfig { } } - /** - * @deprecated use connectToIndyLedgersOnStartup from the `LedgerModuleConfig` class - */ - public get connectToIndyLedgersOnStartup() { - return this.initConfig.connectToIndyLedgersOnStartup ?? true - } - /** * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but @@ -48,13 +41,6 @@ export class AgentConfig { return this.initConfig.publicDidSeed } - /** - * @deprecated use indyLedgers from the `LedgerModuleConfig` class - */ - public get indyLedgers() { - return this.initConfig.indyLedgers ?? [] - } - /** * @todo move to context configuration */ diff --git a/packages/core/src/agent/AgentDependencies.ts b/packages/core/src/agent/AgentDependencies.ts index 1aa681645d..be1146e818 100644 --- a/packages/core/src/agent/AgentDependencies.ts +++ b/packages/core/src/agent/AgentDependencies.ts @@ -1,6 +1,5 @@ import type { FileSystem } from '../storage/FileSystem' import type { EventEmitter } from 'events' -import type * as Indy from 'indy-sdk' import type fetch from 'node-fetch' import type WebSocket from 'ws' @@ -8,7 +7,6 @@ export interface AgentDependencies { FileSystem: { new (): FileSystem } - indy: typeof Indy EventEmitterClass: typeof EventEmitter fetch: typeof fetch WebSocketClass: typeof WebSocket diff --git a/packages/core/src/agent/AgentModules.ts b/packages/core/src/agent/AgentModules.ts index 20f0d6cbb7..aa3ab239c6 100644 --- a/packages/core/src/agent/AgentModules.ts +++ b/packages/core/src/agent/AgentModules.ts @@ -10,8 +10,6 @@ import { CredentialsModule } from '../modules/credentials' import { DidsModule } from '../modules/dids' import { DiscoverFeaturesModule } from '../modules/discover-features' import { GenericRecordsModule } from '../modules/generic-records' -import { IndyModule } from '../modules/indy' -import { LedgerModule } from '../modules/ledger' import { OutOfBandModule } from '../modules/oob' import { ProofsModule } from '../modules/proofs' import { MediatorModule, RecipientModule } from '../modules/routing' @@ -149,16 +147,10 @@ function getDefaultAgentModules(agentConfig: AgentConfig) { }), basicMessages: () => new BasicMessagesModule(), genericRecords: () => new GenericRecordsModule(), - ledger: () => - new LedgerModule({ - connectToIndyLedgersOnStartup: agentConfig.connectToIndyLedgersOnStartup, - indyLedgers: agentConfig.indyLedgers, - }), discovery: () => new DiscoverFeaturesModule(), dids: () => new DidsModule(), wallet: () => new WalletModule(), oob: () => new OutOfBandModule(), - indy: () => new IndyModule(), w3cVc: () => new W3cVcModule(), cache: () => new CacheModule(), } as const diff --git a/packages/core/src/agent/BaseAgent.ts b/packages/core/src/agent/BaseAgent.ts index 704b2b5d98..e5e59bd7f4 100644 --- a/packages/core/src/agent/BaseAgent.ts +++ b/packages/core/src/agent/BaseAgent.ts @@ -13,7 +13,6 @@ import { CredentialsApi } from '../modules/credentials' import { DidsApi } from '../modules/dids' import { DiscoverFeaturesApi } from '../modules/discover-features' import { GenericRecordsApi } from '../modules/generic-records' -import { LedgerApi } from '../modules/ledger' import { OutOfBandApi } from '../modules/oob' import { ProofsApi } from '../modules/proofs' import { MediatorApi, RecipientApi } from '../modules/routing' @@ -50,7 +49,6 @@ export abstract class BaseAgent { expect(container.resolve(MediatorService)).toBeInstanceOf(MediatorService) expect(container.resolve(MediationRecipientService)).toBeInstanceOf(MediationRecipientService) - expect(container.resolve(LedgerApi)).toBeInstanceOf(LedgerApi) - expect(container.resolve(IndyLedgerService)).toBeInstanceOf(IndyLedgerService) - // Symbols, interface based expect(container.resolve(InjectionSymbols.Logger)).toBe(agentOptions.config.logger) expect(container.resolve(InjectionSymbols.MessageRepository)).toBeInstanceOf(InMemoryMessageRepository) - expect(container.resolve(InjectionSymbols.StorageService)).toBeInstanceOf(IndyStorageService) // Agent expect(container.resolve(MessageSender)).toBeInstanceOf(MessageSender) @@ -218,9 +209,6 @@ describe('Agent', () => { expect(container.resolve(MediatorService)).toBe(container.resolve(MediatorService)) expect(container.resolve(MediationRecipientService)).toBe(container.resolve(MediationRecipientService)) - expect(container.resolve(LedgerApi)).toBe(container.resolve(LedgerApi)) - expect(container.resolve(IndyLedgerService)).toBe(container.resolve(IndyLedgerService)) - // Symbols, interface based expect(container.resolve(InjectionSymbols.Logger)).toBe(container.resolve(InjectionSymbols.Logger)) expect(container.resolve(InjectionSymbols.MessageRepository)).toBe( diff --git a/packages/core/src/agent/__tests__/AgentModules.test.ts b/packages/core/src/agent/__tests__/AgentModules.test.ts index 60755c487e..7ee76dfe6f 100644 --- a/packages/core/src/agent/__tests__/AgentModules.test.ts +++ b/packages/core/src/agent/__tests__/AgentModules.test.ts @@ -8,8 +8,6 @@ import { CredentialsModule } from '../../modules/credentials' import { DidsModule } from '../../modules/dids' import { DiscoverFeaturesModule } from '../../modules/discover-features' import { GenericRecordsModule } from '../../modules/generic-records' -import { IndyModule } from '../../modules/indy' -import { LedgerModule } from '../../modules/ledger' import { OutOfBandModule } from '../../modules/oob' import { ProofsModule } from '../../modules/proofs' import { MediatorModule, RecipientModule } from '../../modules/routing' @@ -66,12 +64,10 @@ describe('AgentModules', () => { mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), genericRecords: expect.any(GenericRecordsModule), - ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), dids: expect.any(DidsModule), wallet: expect.any(WalletModule), oob: expect.any(OutOfBandModule), - indy: expect.any(IndyModule), w3cVc: expect.any(W3cVcModule), cache: expect.any(CacheModule), }) @@ -91,12 +87,10 @@ describe('AgentModules', () => { mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), genericRecords: expect.any(GenericRecordsModule), - ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), dids: expect.any(DidsModule), wallet: expect.any(WalletModule), oob: expect.any(OutOfBandModule), - indy: expect.any(IndyModule), w3cVc: expect.any(W3cVcModule), cache: expect.any(CacheModule), myModule, @@ -119,12 +113,10 @@ describe('AgentModules', () => { mediationRecipient: expect.any(RecipientModule), basicMessages: expect.any(BasicMessagesModule), genericRecords: expect.any(GenericRecordsModule), - ledger: expect.any(LedgerModule), discovery: expect.any(DiscoverFeaturesModule), dids: expect.any(DidsModule), wallet: expect.any(WalletModule), oob: expect.any(OutOfBandModule), - indy: expect.any(IndyModule), w3cVc: expect.any(W3cVcModule), cache: expect.any(CacheModule), myModule, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 2f3142a00a..f82ac57ca9 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -30,6 +30,7 @@ export type { export { DidCommMimeType, KeyDerivationMethod } from './types' export type { FileSystem, DownloadToFileOptions } from './storage/FileSystem' export * from './storage/BaseRecord' +export { DidCommMessageRecord, DidCommMessageRole, DidCommMessageRepository } from './storage/didcomm' export { InMemoryMessageRepository } from './storage/InMemoryMessageRepository' export { Repository } from './storage/Repository' export * from './storage/RepositoryEvents' @@ -41,6 +42,7 @@ export * from './wallet' export type { TransportSession } from './agent/TransportService' export { TransportService } from './agent/TransportService' export { Attachment, AttachmentData } from './decorators/attachment/Attachment' +export { ServiceDecorator, ServiceDecoratorOptions } from './decorators/service/ServiceDecorator' export { ReturnRouteTypes } from './decorators/transport/TransportDecorator' export * from './plugins' @@ -52,7 +54,6 @@ export * from './modules/discover-features' export * from './modules/problem-reports' export * from './modules/proofs' export * from './modules/connections' -export * from './modules/ledger' export * from './modules/routing' export * from './modules/oob' export * from './modules/dids' @@ -62,7 +63,7 @@ export { JsonEncoder, JsonTransformer, isJsonObject, isValidJweStructure, TypedA export * from './logger' export * from './error' export * from './wallet/error' -export { parseMessageType, IsValidMessageType } from './utils/messageType' +export { parseMessageType, IsValidMessageType, replaceLegacyDidSovPrefix } from './utils/messageType' export type { Constructor } from './utils/mixins' export * from './agent/Events' export * from './crypto/' diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index a7d762e248..47e7ccda00 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -9,7 +9,6 @@ import { Protocol } from '../../agent/models' import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' -import { IndyCredentialFormatService } from './formats/indy' import { RevocationNotificationService } from './protocol/revocation-notification/services' import { V1CredentialProtocol } from './protocol/v1' import { V2CredentialProtocol } from './protocol/v2' @@ -18,7 +17,7 @@ import { CredentialRepository } from './repository' /** * Default credentialProtocols that will be registered if the `credentialProtocols` property is not configured. */ -export type DefaultCredentialProtocols = [V1CredentialProtocol, V2CredentialProtocol] +export type DefaultCredentialProtocols = [V1CredentialProtocol, V2CredentialProtocol<[]>] // CredentialsModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. export type CredentialsModuleOptions = Optional< @@ -39,26 +38,11 @@ export class CredentialsModule } - /** - * Get the default credential protocols that will be registered if the `credentialProtocols` property is not configured. - */ - private getDefaultCredentialProtocols(): DefaultCredentialProtocols { - // Instantiate credential formats - const indyCredentialFormat = new IndyCredentialFormatService() - - // Instantiate credential protocols - const v1CredentialProtocol = new V1CredentialProtocol({ indyCredentialFormat }) - const v2CredentialProtocol = new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormat], - }) - - return [v1CredentialProtocol, v2CredentialProtocol] - } - /** * Registers the dependencies of the credentials module on the dependency manager. */ diff --git a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts deleted file mode 100644 index bbe6f379f8..0000000000 --- a/packages/core/src/modules/credentials/formats/__tests__/IndyCredentialFormatService.test.ts +++ /dev/null @@ -1,443 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { AgentConfig } from '../../../../agent/AgentConfig' -import type { ParseRevocationRegistryDefinitionTemplate } from '../../../ledger/services/IndyLedgerService' -import type { CredentialFormatService } from '../../formats' -import type { IndyCredentialFormat } from '../../formats/indy/IndyCredentialFormat' -import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' -import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' -import type { CustomCredentialTags } from '../../repository/CredentialExchangeRecord' -import type { RevocRegDef } from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' -import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' -import { JsonEncoder } from '../../../../utils/JsonEncoder' -import { ConnectionService } from '../../../connections/services/ConnectionService' -import { DidResolverService } from '../../../dids/services/DidResolverService' -import { IndyHolderService } from '../../../indy/services/IndyHolderService' -import { IndyIssuerService } from '../../../indy/services/IndyIssuerService' -import { IndyLedgerService } from '../../../ledger/services/IndyLedgerService' -import { credDef, credReq, schema } from '../../__tests__/fixtures' -import { IndyCredentialFormatService } from '../../formats' -import { IndyCredentialUtils } from '../../formats/indy/IndyCredentialUtils' -import { CredentialState } from '../../models' -import { - INDY_CREDENTIAL_ATTACHMENT_ID, - INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, -} from '../../protocol/v1/messages' -import { V2CredentialPreview } from '../../protocol/v2/messages' -import { V2OfferCredentialMessage } from '../../protocol/v2/messages/V2OfferCredentialMessage' -import { CredentialMetadataKeys } from '../../repository' -import { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' - -jest.mock('../../../../modules/ledger/services/IndyLedgerService') -jest.mock('../../../indy/services/IndyHolderService') -jest.mock('../../../indy/services/IndyIssuerService') -jest.mock('../../../dids/services/DidResolverService') -jest.mock('../../../connections/services/ConnectionService') - -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const IndyHolderServiceMock = IndyHolderService as jest.Mock -const IndyIssuerServiceMock = IndyIssuerService as jest.Mock -const ConnectionServiceMock = ConnectionService as jest.Mock -const DidResolverServiceMock = DidResolverService as jest.Mock - -const values = { - x: { - raw: 'x', - encoded: 'y', - }, -} -const cred = { - schema_id: 'xsxs', - cred_def_id: 'xdxd', - rev_reg_id: 'x', - values: values, - signature: undefined, - signature_correctness_proof: undefined, -} - -const revDef: RevocRegDef = { - id: 'x', - revocDefType: 'CL_ACCUM', - tag: 'x', - credDefId: 'x', - value: { - issuanceType: 'ISSUANCE_BY_DEFAULT', - maxCredNum: 33, - tailsHash: 'd', - tailsLocation: 'x', - publicKeys: { - accumKey: { - z: 'x', - }, - }, - }, - ver: 't', -} - -const revocationTemplate: ParseRevocationRegistryDefinitionTemplate = { - revocationRegistryDefinition: revDef, - revocationRegistryDefinitionTxnTime: 42, -} - -const credentialPreview = V2CredentialPreview.fromRecord({ - name: 'John', - age: '99', -}) - -const offerAttachment = new Attachment({ - id: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: - 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', - }), -}) - -const requestAttachment = new Attachment({ - id: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(credReq), - }), -}) - -const credentialAttachment = new Attachment({ - id: INDY_CREDENTIAL_ATTACHMENT_ID, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64({ - values: IndyCredentialUtils.convertAttributesToValues(credentialPreview.attributes), - }), - }), -}) - -// A record is deserialized to JSON when it's stored into the storage. We want to simulate this behaviour for `offer` -// object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. -const mockCredentialRecord = ({ - state, - metadata, - threadId, - connectionId, - tags, - id, - credentialAttributes, -}: { - state?: CredentialState - metadata?: { indyRequest: Record } - tags?: CustomCredentialTags - threadId?: string - connectionId?: string - id?: string - credentialAttributes?: CredentialPreviewAttribute[] -} = {}) => { - const offerOptions: V2OfferCredentialMessageOptions = { - id: '', - formats: [ - { - attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - format: 'hlindy/cred-abstract@v2.0', - }, - ], - comment: 'some comment', - credentialPreview: credentialPreview, - offerAttachments: [offerAttachment], - replacementId: undefined, - } - const offerMessage = new V2OfferCredentialMessage(offerOptions) - - const credentialRecord = new CredentialExchangeRecord({ - id, - credentialAttributes: credentialAttributes || credentialPreview.attributes, - state: state || CredentialState.OfferSent, - threadId: threadId ?? offerMessage.id, - connectionId: connectionId ?? '123', - tags, - protocolVersion: 'v2', - }) - - if (metadata?.indyRequest) { - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) - } - - return credentialRecord -} -let indyFormatService: CredentialFormatService -let indyLedgerService: IndyLedgerService -let indyIssuerService: IndyIssuerService -let indyHolderService: IndyHolderService -let didResolverService: DidResolverService -let connectionService: ConnectionService -let agentConfig: AgentConfig -let credentialRecord: CredentialExchangeRecord - -describe('Indy CredentialFormatService', () => { - let agentContext: AgentContext - beforeEach(async () => { - indyIssuerService = new IndyIssuerServiceMock() - indyHolderService = new IndyHolderServiceMock() - indyLedgerService = new IndyLedgerServiceMock() - didResolverService = new DidResolverServiceMock() - connectionService = new ConnectionServiceMock() - - agentConfig = getAgentConfig('IndyCredentialFormatServiceTest') - agentContext = getAgentContext({ - registerInstances: [ - [IndyIssuerService, indyIssuerService], - [IndyHolderService, indyHolderService], - [IndyLedgerService, indyLedgerService], - [DidResolverService, didResolverService], - [ConnectionService, connectionService], - ], - agentConfig, - }) - - indyFormatService = new IndyCredentialFormatService() - - mockFunction(indyLedgerService.getSchema).mockReturnValue(Promise.resolve(schema)) - }) - - describe('Create Credential Proposal / Offer', () => { - test(`Creates Credential Proposal`, async () => { - // when - const { attachment, previewAttributes, format } = await indyFormatService.createProposal(agentContext, { - credentialRecord: mockCredentialRecord(), - credentialFormats: { - indy: { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', - schemaName: 'ahoy', - schemaVersion: '1.0', - schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', - attributes: credentialPreview.attributes, - }, - }, - }) - - // then - expect(attachment).toMatchObject({ - id: expect.any(String), - description: undefined, - filename: undefined, - mimeType: 'application/json', - lastmodTime: undefined, - byteCount: undefined, - data: { - base64: - 'eyJzY2hlbWFfaXNzdWVyX2RpZCI6IkdNbTR2TXc4TExyTEpqcDgxa1JSTHAiLCJzY2hlbWFfaWQiOiJxN0FUd1RZYlFEZ2lpZ1ZpalVBZWo6Mjp0ZXN0OjEuMCIsInNjaGVtYV9uYW1lIjoiYWhveSIsInNjaGVtYV92ZXJzaW9uIjoiMS4wIiwiY3JlZF9kZWZfaWQiOiJUaDdNcFRhUlpWUlluUGlhYmRzODFZOjM6Q0w6MTc6VEFHIiwiaXNzdWVyX2RpZCI6IkdNbTR2TXc4TExyTEpqcDgxa1JSTHAifQ==', - json: undefined, - links: undefined, - jws: undefined, - sha256: undefined, - }, - }) - - expect(previewAttributes).toMatchObject([ - { - mimeType: 'text/plain', - name: 'name', - value: 'John', - }, - { - mimeType: 'text/plain', - name: 'age', - value: '99', - }, - ]) - - expect(format).toMatchObject({ - attachmentId: expect.any(String), - format: 'hlindy/cred-filter@v2.0', - }) - }) - - test(`Creates Credential Offer`, async () => { - // when - const { attachment, previewAttributes, format } = await indyFormatService.createOffer(agentContext, { - credentialRecord: mockCredentialRecord(), - credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - }, - }, - }) - - // then - expect(indyIssuerService.createCredentialOffer).toHaveBeenCalledTimes(1) - - expect(attachment).toMatchObject({ - id: expect.any(String), - description: undefined, - filename: undefined, - mimeType: 'application/json', - lastmodTime: undefined, - byteCount: undefined, - data: { - base64: - 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0=', - json: undefined, - links: undefined, - jws: undefined, - sha256: undefined, - }, - }) - - expect(previewAttributes).toMatchObject([ - { - mimeType: 'text/plain', - name: 'name', - value: 'John', - }, - { - mimeType: 'text/plain', - name: 'age', - value: '99', - }, - ]) - - expect(format).toMatchObject({ - attachmentId: expect.any(String), - format: 'hlindy/cred-abstract@v2.0', - }) - }) - }) - describe('Process Credential Offer', () => { - test(`processes credential offer - returns modified credential record (adds metadata)`, async () => { - // given - const credentialRecord = mockCredentialRecord({ - state: CredentialState.OfferReceived, - threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', - connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', - }) - - // when - await indyFormatService.processOffer(agentContext, { attachment: offerAttachment, credentialRecord }) - }) - }) - - describe('Create Credential Request', () => { - test('returns credential request message base on existing credential offer message', async () => { - // given - const credentialRecord = mockCredentialRecord({ - state: CredentialState.OfferReceived, - threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', - connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', - }) - - mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) - - // when - const { format, attachment } = await indyFormatService.acceptOffer(agentContext, { - credentialRecord, - credentialFormats: { - indy: { - holderDid: 'holderDid', - }, - }, - offerAttachment, - }) - - // then - expect(indyHolderService.createCredentialRequest).toHaveBeenCalledTimes(1) - - expect(attachment).toMatchObject({ - id: expect.any(String), - description: undefined, - filename: undefined, - mimeType: 'application/json', - lastmodTime: undefined, - byteCount: undefined, - data: { - base64: - 'eyJwcm92ZXJfZGlkIjoiaG9sZGVyRGlkIiwiY3JlZF9kZWZfaWQiOiJUTDFFYVBGQ1o4U2k1YVVycVNjQkR0OjM6Q0w6MTY6VEFHIiwiYmxpbmRlZF9tcyI6e30sImJsaW5kZWRfbXNfY29ycmVjdG5lc3NfcHJvb2YiOnt9LCJub25jZSI6Im5vbmNlIn0=', - json: undefined, - links: undefined, - jws: undefined, - sha256: undefined, - }, - }) - expect(format).toMatchObject({ - attachmentId: expect.any(String), - format: 'hlindy/cred-req@v2.0', - }) - - const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyCredential) - - expect(credentialRequestMetadata?.schemaId).toBe('aaa') - expect(credentialRequestMetadata?.credentialDefinitionId).toBe('Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG') - }) - }) - - describe('Accept request', () => { - test('Creates a credentials', async () => { - // given - const credentialRecord = mockCredentialRecord({ - state: CredentialState.RequestReceived, - threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', - connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', - }) - - mockFunction(indyIssuerService.createCredential).mockReturnValue(Promise.resolve([cred, 'x'])) - - // when - const { format, attachment } = await indyFormatService.acceptRequest(agentContext, { - credentialRecord, - requestAttachment, - offerAttachment, - attachmentId: INDY_CREDENTIAL_ATTACHMENT_ID, - }) - - expect(attachment).toMatchObject({ - id: 'libindy-cred-0', - description: undefined, - filename: undefined, - mimeType: 'application/json', - lastmodTime: undefined, - byteCount: undefined, - data: { - base64: - 'eyJzY2hlbWFfaWQiOiJ4c3hzIiwiY3JlZF9kZWZfaWQiOiJ4ZHhkIiwicmV2X3JlZ19pZCI6IngiLCJ2YWx1ZXMiOnsieCI6eyJyYXciOiJ4IiwiZW5jb2RlZCI6InkifX19', - json: undefined, - links: undefined, - jws: undefined, - sha256: undefined, - }, - }) - expect(format).toMatchObject({ - attachmentId: expect.any(String), - format: 'hlindy/cred@v2.0', - }) - }) - }) - - describe('Process Credential', () => { - test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { - // given - credentialRecord = mockCredentialRecord({ - state: CredentialState.RequestSent, - metadata: { indyRequest: { cred_req: 'meta-data' } }, - }) - mockFunction(indyLedgerService.getCredentialDefinition).mockReturnValue(Promise.resolve(credDef)) - mockFunction(indyLedgerService.getRevocationRegistryDefinition).mockReturnValue( - Promise.resolve(revocationTemplate) - ) - mockFunction(indyHolderService.storeCredential).mockReturnValue(Promise.resolve('100')) - - // when - await indyFormatService.processCredential(agentContext, { - attachment: credentialAttachment, - requestAttachment: requestAttachment, - credentialRecord, - }) - - // then - expect(indyHolderService.storeCredential).toHaveBeenCalledTimes(1) - expect(credentialRecord.credentials.length).toBe(1) - expect(credentialRecord.credentials[0].credentialRecordType).toBe('indy') - expect(credentialRecord.credentials[0].credentialRecordId).toBe('100') - }) - }) -}) diff --git a/packages/core/src/modules/credentials/formats/index.ts b/packages/core/src/modules/credentials/formats/index.ts index 614b3559a3..fb2b300b5e 100644 --- a/packages/core/src/modules/credentials/formats/index.ts +++ b/packages/core/src/modules/credentials/formats/index.ts @@ -1,5 +1,4 @@ export * from './CredentialFormatService' export * from './CredentialFormatServiceOptions' export * from './CredentialFormat' -export * from './indy' export * from './jsonld' diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts deleted file mode 100644 index 73c8082372..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormat.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { IndyCredProposeOptions } from './models/IndyCredPropose' -import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' -import type { CredentialPreviewAttributeOptions } from '../../models' -import type { CredentialFormat } from '../CredentialFormat' -import type { Cred, CredOffer, CredReq } from 'indy-sdk' - -/** - * This defines the module payload for calling CredentialsApi.createProposal - * or CredentialsApi.negotiateOffer - */ -export interface IndyProposeCredentialFormat extends IndyCredProposeOptions { - attributes?: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] -} - -/** - * This defines the module payload for calling CredentialsApi.acceptProposal - */ -export interface IndyAcceptProposalFormat { - credentialDefinitionId?: string - attributes?: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] -} - -export interface IndyAcceptOfferFormat { - holderDid?: string -} - -/** - * This defines the module payload for calling CredentialsApi.offerCredential - * or CredentialsApi.negotiateProposal - */ -export interface IndyOfferCredentialFormat { - credentialDefinitionId: string - attributes: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] -} - -export interface IndyCredentialFormat extends CredentialFormat { - formatKey: 'indy' - credentialRecordType: 'indy' - credentialFormats: { - createProposal: IndyProposeCredentialFormat - acceptProposal: IndyAcceptProposalFormat - createOffer: IndyOfferCredentialFormat - acceptOffer: IndyAcceptOfferFormat - createRequest: never // cannot start from createRequest - acceptRequest: Record // empty object - } - // Format data is based on RFC 0592 - // https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments - formatData: { - proposal: { - schema_issuer_did?: string - schema_name?: string - schema_version?: string - schema_id?: string - issuer_did?: string - cred_def_id?: string - } - offer: CredOffer - request: CredReq - credential: Cred - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts deleted file mode 100644 index bee27e8983..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ /dev/null @@ -1,594 +0,0 @@ -import type { IndyCredentialFormat } from './IndyCredentialFormat' -import type { AgentContext } from '../../../../agent' -import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' -import type { CredentialPreviewAttributeOptions } from '../../models/CredentialPreviewAttribute' -import type { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' -import type { CredentialFormatService } from '../CredentialFormatService' -import type { - CredentialFormatAcceptOfferOptions, - CredentialFormatAcceptProposalOptions, - CredentialFormatAcceptRequestOptions, - CredentialFormatAutoRespondCredentialOptions, - CredentialFormatAutoRespondOfferOptions, - CredentialFormatAutoRespondProposalOptions, - CredentialFormatAutoRespondRequestOptions, - CredentialFormatCreateOfferOptions, - CredentialFormatCreateOfferReturn, - CredentialFormatCreateProposalOptions, - CredentialFormatCreateProposalReturn, - CredentialFormatCreateReturn, - CredentialFormatProcessOptions, - CredentialFormatProcessCredentialOptions, -} from '../CredentialFormatServiceOptions' -import type * as Indy from 'indy-sdk' - -import { KeyType } from '../../../../crypto' -import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../error' -import { JsonEncoder } from '../../../../utils/JsonEncoder' -import { JsonTransformer } from '../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { TypedArrayEncoder } from '../../../../utils/TypedArrayEncoder' -import { getIndyDidFromVerificationMethod } from '../../../../utils/did' -import { uuid } from '../../../../utils/uuid' -import { ConnectionService } from '../../../connections' -import { DidResolverService, findVerificationMethodByKeyType } from '../../../dids' -import { IndyHolderService } from '../../../indy/services/IndyHolderService' -import { IndyIssuerService } from '../../../indy/services/IndyIssuerService' -import { IndyLedgerService } from '../../../ledger' -import { CredentialProblemReportError, CredentialProblemReportReason } from '../../errors' -import { CredentialFormatSpec } from '../../models/CredentialFormatSpec' -import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' -import { CredentialMetadataKeys } from '../../repository/CredentialMetadataTypes' - -import { IndyCredentialUtils } from './IndyCredentialUtils' -import { IndyCredPropose } from './models/IndyCredPropose' - -const INDY_CRED_ABSTRACT = 'hlindy/cred-abstract@v2.0' -const INDY_CRED_REQUEST = 'hlindy/cred-req@v2.0' -const INDY_CRED_FILTER = 'hlindy/cred-filter@v2.0' -const INDY_CRED = 'hlindy/cred@v2.0' - -export class IndyCredentialFormatService implements CredentialFormatService { - public readonly formatKey = 'indy' as const - public readonly credentialRecordType = 'indy' as const - - /** - * Create a {@link AttachmentFormats} object dependent on the message type. - * - * @param options The object containing all the options for the proposed credential - * @returns object containing associated attachment, format and optionally the credential preview - * - */ - public async createProposal( - agentContext: AgentContext, - { credentialFormats, credentialRecord, attachmentId }: CredentialFormatCreateProposalOptions - ): Promise { - const format = new CredentialFormatSpec({ - format: INDY_CRED_FILTER, - attachmentId, - }) - - const indyFormat = credentialFormats.indy - - if (!indyFormat) { - throw new AriesFrameworkError('Missing indy payload in createProposal') - } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { attributes, linkedAttachments, ...indyCredentialProposal } = indyFormat - - const proposal = new IndyCredPropose(indyCredentialProposal) - - try { - MessageValidator.validateSync(proposal) - } catch (error) { - throw new AriesFrameworkError(`Invalid proposal supplied: ${indyCredentialProposal} in Indy Format Service`) - } - - const proposalJson = JsonTransformer.toJSON(proposal) - const attachment = this.getFormatData(proposalJson, format.attachmentId) - - const { previewAttributes } = this.getCredentialLinkedAttachments( - indyFormat.attributes, - indyFormat.linkedAttachments - ) - - // Set the metadata - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: proposal.schemaId, - credentialDefinitionId: proposal.credentialDefinitionId, - }) - - return { format, attachment, previewAttributes } - } - - public async processProposal( - agentContext: AgentContext, - { attachment }: CredentialFormatProcessOptions - ): Promise { - const proposalJson = attachment.getDataAsJson() - - // fromJSON also validates - JsonTransformer.fromJSON(proposalJson, IndyCredPropose) - } - - public async acceptProposal( - agentContext: AgentContext, - { - attachmentId, - credentialFormats, - credentialRecord, - proposalAttachment, - }: CredentialFormatAcceptProposalOptions - ): Promise { - const indyFormat = credentialFormats?.indy - - const credentialProposal = JsonTransformer.fromJSON(proposalAttachment.getDataAsJson(), IndyCredPropose) - - const credentialDefinitionId = indyFormat?.credentialDefinitionId ?? credentialProposal.credentialDefinitionId - const attributes = indyFormat?.attributes ?? credentialRecord.credentialAttributes - - if (!credentialDefinitionId) { - throw new AriesFrameworkError( - 'No credentialDefinitionId in proposal or provided as input to accept proposal method.' - ) - } - - if (!attributes) { - throw new AriesFrameworkError('No attributes in proposal or provided as input to accept proposal method.') - } - - const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { - credentialRecord, - attachmentId, - attributes, - credentialDefinitionId: credentialDefinitionId, - linkedAttachments: indyFormat?.linkedAttachments, - }) - - return { format, attachment, previewAttributes } - } - - /** - * Create a credential attachment format for a credential request. - * - * @param options The object containing all the options for the credential offer - * @returns object containing associated attachment, formats and offersAttach elements - * - */ - public async createOffer( - agentContext: AgentContext, - { credentialFormats, credentialRecord, attachmentId }: CredentialFormatCreateOfferOptions - ): Promise { - const indyFormat = credentialFormats.indy - - if (!indyFormat) { - throw new AriesFrameworkError('Missing indy credentialFormat data') - } - - const { format, attachment, previewAttributes } = await this.createIndyOffer(agentContext, { - credentialRecord, - attachmentId, - attributes: indyFormat.attributes, - credentialDefinitionId: indyFormat.credentialDefinitionId, - linkedAttachments: indyFormat.linkedAttachments, - }) - - return { format, attachment, previewAttributes } - } - - public async processOffer( - agentContext: AgentContext, - { attachment, credentialRecord }: CredentialFormatProcessOptions - ) { - agentContext.config.logger.debug(`Processing indy credential offer for credential record ${credentialRecord.id}`) - - const credOffer = attachment.getDataAsJson() - - if (!credOffer.schema_id || !credOffer.cred_def_id) { - throw new CredentialProblemReportError('Invalid credential offer', { - problemCode: CredentialProblemReportReason.IssuanceAbandoned, - }) - } - } - - public async acceptOffer( - agentContext: AgentContext, - { - credentialFormats, - credentialRecord, - attachmentId, - offerAttachment, - }: CredentialFormatAcceptOfferOptions - ): Promise { - const indyFormat = credentialFormats?.indy - - const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) - const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) - - const holderDid = indyFormat?.holderDid ?? (await this.getIndyHolderDid(agentContext, credentialRecord)) - - const credentialOffer = offerAttachment.getDataAsJson() - const credentialDefinition = await indyLedgerService.getCredentialDefinition( - agentContext, - credentialOffer.cred_def_id - ) - - const [credentialRequest, credentialRequestMetadata] = await indyHolderService.createCredentialRequest( - agentContext, - { - holderDid, - credentialOffer, - credentialDefinition, - } - ) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, credentialRequestMetadata) - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: credentialOffer.cred_def_id, - schemaId: credentialOffer.schema_id, - }) - - const format = new CredentialFormatSpec({ - attachmentId, - format: INDY_CRED_REQUEST, - }) - - const attachment = this.getFormatData(credentialRequest, format.attachmentId) - return { format, attachment } - } - - /** - * Starting from a request is not supported for indy credentials, this method only throws an error. - */ - public async createRequest(): Promise { - throw new AriesFrameworkError('Starting from a request is not supported for indy credentials') - } - - /** - * We don't have any models to validate an indy request object, for now this method does nothing - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async processRequest(agentContext: AgentContext, options: CredentialFormatProcessOptions): Promise { - // not needed for Indy - } - - public async acceptRequest( - agentContext: AgentContext, - { - credentialRecord, - attachmentId, - offerAttachment, - requestAttachment, - }: CredentialFormatAcceptRequestOptions - ): Promise { - // Assert credential attributes - const credentialAttributes = credentialRecord.credentialAttributes - if (!credentialAttributes) { - throw new CredentialProblemReportError( - `Missing required credential attribute values on credential record with id ${credentialRecord.id}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - const indyIssuerService = agentContext.dependencyManager.resolve(IndyIssuerService) - - const credentialOffer = offerAttachment?.getDataAsJson() - const credentialRequest = requestAttachment.getDataAsJson() - - if (!credentialOffer || !credentialRequest) { - throw new AriesFrameworkError('Missing indy credential offer or credential request in createCredential') - } - - const [credential, credentialRevocationId] = await indyIssuerService.createCredential(agentContext, { - credentialOffer, - credentialRequest, - credentialValues: IndyCredentialUtils.convertAttributesToValues(credentialAttributes), - }) - - if (credential.rev_reg_id) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - indyCredentialRevocationId: credentialRevocationId, - indyRevocationRegistryId: credential.rev_reg_id, - }) - } - - const format = new CredentialFormatSpec({ - attachmentId, - format: INDY_CRED, - }) - - const attachment = this.getFormatData(credential, format.attachmentId) - return { format, attachment } - } - - /** - * Processes an incoming credential - retrieve metadata, retrieve payload and store it in the Indy wallet - * @param options the issue credential message wrapped inside this object - * @param credentialRecord the credential exchange record for this credential - */ - public async processCredential( - agentContext: AgentContext, - { credentialRecord, attachment }: CredentialFormatProcessCredentialOptions - ): Promise { - const credentialRequestMetadata = credentialRecord.metadata.get(CredentialMetadataKeys.IndyRequest) - - const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) - const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) - - if (!credentialRequestMetadata) { - throw new CredentialProblemReportError( - `Missing required request metadata for credential with id ${credentialRecord.id}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } - ) - } - - const indyCredential = attachment.getDataAsJson() - const credentialDefinition = await indyLedgerService.getCredentialDefinition( - agentContext, - indyCredential.cred_def_id - ) - const revocationRegistry = indyCredential.rev_reg_id - ? await indyLedgerService.getRevocationRegistryDefinition(agentContext, indyCredential.rev_reg_id) - : null - - if (!credentialRecord.credentialAttributes) { - throw new AriesFrameworkError( - 'Missing credential attributes on credential record. Unable to check credential attributes' - ) - } - - // assert the credential values match the offer values - const recordCredentialValues = IndyCredentialUtils.convertAttributesToValues(credentialRecord.credentialAttributes) - IndyCredentialUtils.assertValuesMatch(indyCredential.values, recordCredentialValues) - - const credentialId = await indyHolderService.storeCredential(agentContext, { - credentialId: uuid(), - credentialRequestMetadata, - credential: indyCredential, - credentialDefinition, - revocationRegistryDefinition: revocationRegistry?.revocationRegistryDefinition, - }) - - // If the credential is revocable, store the revocation identifiers in the credential record - if (indyCredential.rev_reg_id) { - const credential = await indyHolderService.getCredential(agentContext, credentialId) - - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - indyCredentialRevocationId: credential.cred_rev_id, - indyRevocationRegistryId: indyCredential.rev_reg_id, - }) - } - - credentialRecord.credentials.push({ - credentialRecordType: this.credentialRecordType, - credentialRecordId: credentialId, - }) - } - - public supportsFormat(format: string): boolean { - const supportedFormats = [INDY_CRED_ABSTRACT, INDY_CRED_REQUEST, INDY_CRED_FILTER, INDY_CRED] - - return supportedFormats.includes(format) - } - - /** - * Gets the attachment object for a given attachmentId. We need to get out the correct attachmentId for - * indy and then find the corresponding attachment (if there is one) - * @param formats the formats object containing the attachmentId - * @param messageAttachments the attachments containing the payload - * @returns The Attachment if found or undefined - * - */ - public getAttachment(formats: CredentialFormatSpec[], messageAttachments: Attachment[]): Attachment | undefined { - const supportedAttachmentIds = formats.filter((f) => this.supportsFormat(f.format)).map((f) => f.attachmentId) - const supportedAttachments = messageAttachments.filter((attachment) => - supportedAttachmentIds.includes(attachment.id) - ) - - return supportedAttachments[0] - } - - public async deleteCredentialById(agentContext: AgentContext, credentialRecordId: string): Promise { - const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) - - await indyHolderService.deleteCredential(agentContext, credentialRecordId) - } - - public async shouldAutoRespondToProposal( - agentContext: AgentContext, - { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondProposalOptions - ) { - const credentialProposalJson = proposalAttachment.getDataAsJson() - const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) - - const credentialOfferJson = offerAttachment.getDataAsJson() - - // We want to make sure the credential definition matches. - // TODO: If no credential definition is present on the proposal, we could check whether the other fields - // of the proposal match with the credential definition id. - return credentialProposal.credentialDefinitionId === credentialOfferJson.cred_def_id - } - - public async shouldAutoRespondToOffer( - agentContext: AgentContext, - { offerAttachment, proposalAttachment }: CredentialFormatAutoRespondOfferOptions - ) { - const credentialProposalJson = proposalAttachment.getDataAsJson() - const credentialProposal = JsonTransformer.fromJSON(credentialProposalJson, IndyCredPropose) - - const credentialOfferJson = offerAttachment.getDataAsJson() - - // We want to make sure the credential definition matches. - // TODO: If no credential definition is present on the proposal, we could check whether the other fields - // of the proposal match with the credential definition id. - return credentialProposal.credentialDefinitionId === credentialOfferJson.cred_def_id - } - - public async shouldAutoRespondToRequest( - agentContext: AgentContext, - { offerAttachment, requestAttachment }: CredentialFormatAutoRespondRequestOptions - ) { - const credentialOfferJson = offerAttachment.getDataAsJson() - const credentialRequestJson = requestAttachment.getDataAsJson() - - return credentialOfferJson.cred_def_id == credentialRequestJson.cred_def_id - } - - public async shouldAutoRespondToCredential( - agentContext: AgentContext, - { credentialRecord, requestAttachment, credentialAttachment }: CredentialFormatAutoRespondCredentialOptions - ) { - const credentialJson = credentialAttachment.getDataAsJson() - const credentialRequestJson = requestAttachment.getDataAsJson() - - // make sure the credential definition matches - if (credentialJson.cred_def_id !== credentialRequestJson.cred_def_id) return false - - // If we don't have any attributes stored we can't compare so always return false. - if (!credentialRecord.credentialAttributes) return false - const attributeValues = IndyCredentialUtils.convertAttributesToValues(credentialRecord.credentialAttributes) - - // check whether the values match the values in the record - return IndyCredentialUtils.checkValuesMatch(attributeValues, credentialJson.values) - } - - private async createIndyOffer( - agentContext: AgentContext, - { - credentialRecord, - attachmentId, - credentialDefinitionId, - attributes, - linkedAttachments, - }: { - credentialDefinitionId: string - credentialRecord: CredentialExchangeRecord - attachmentId?: string - attributes: CredentialPreviewAttributeOptions[] - linkedAttachments?: LinkedAttachment[] - } - ): Promise { - const indyIssuerService = agentContext.dependencyManager.resolve(IndyIssuerService) - - // if the proposal has an attachment Id use that, otherwise the generated id of the formats object - const format = new CredentialFormatSpec({ - attachmentId, - format: INDY_CRED_ABSTRACT, - }) - - const offer = await indyIssuerService.createCredentialOffer(agentContext, credentialDefinitionId) - - const { previewAttributes } = this.getCredentialLinkedAttachments(attributes, linkedAttachments) - if (!previewAttributes) { - throw new AriesFrameworkError('Missing required preview attributes for indy offer') - } - - await this.assertPreviewAttributesMatchSchemaAttributes(agentContext, offer, previewAttributes) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - schemaId: offer.schema_id, - credentialDefinitionId: offer.cred_def_id, - }) - - const attachment = this.getFormatData(offer, format.attachmentId) - - return { format, attachment, previewAttributes } - } - - private async assertPreviewAttributesMatchSchemaAttributes( - agentContext: AgentContext, - offer: Indy.CredOffer, - attributes: CredentialPreviewAttribute[] - ): Promise { - const indyLedgerService = agentContext.dependencyManager.resolve(IndyLedgerService) - - const schema = await indyLedgerService.getSchema(agentContext, offer.schema_id) - - IndyCredentialUtils.checkAttributesMatch(schema, attributes) - } - - private async getIndyHolderDid(agentContext: AgentContext, credentialRecord: CredentialExchangeRecord) { - const connectionService = agentContext.dependencyManager.resolve(ConnectionService) - const didResolver = agentContext.dependencyManager.resolve(DidResolverService) - - // If we have a connection id we try to extract the did from the connection did document. - if (credentialRecord.connectionId) { - const connection = await connectionService.getById(agentContext, credentialRecord.connectionId) - if (!connection.did) { - throw new AriesFrameworkError(`Connection record ${connection.id} has no 'did'`) - } - const resolved = await didResolver.resolve(agentContext, connection.did) - - if (resolved.didDocument) { - const verificationMethod = await findVerificationMethodByKeyType( - 'Ed25519VerificationKey2018', - resolved.didDocument - ) - - if (verificationMethod) { - return getIndyDidFromVerificationMethod(verificationMethod) - } - } - } - - // If it wasn't successful to extract the did from the connection, we'll create a new key (e.g. if using connection-less) - // FIXME: we already create a did for the exchange when using connection-less, but this is on a higher level. We should look at - // a way to reuse this key, but for now this is easier. - const key = await agentContext.wallet.createKey({ keyType: KeyType.Ed25519 }) - const did = TypedArrayEncoder.toBase58(key.publicKey.slice(0, 16)) - - return did - } - - /** - * Get linked attachments for indy format from a proposal message. This allows attachments - * to be copied across to old style credential records - * - * @param options ProposeCredentialOptions object containing (optionally) the linked attachments - * @return array of linked attachments or undefined if none present - */ - private getCredentialLinkedAttachments( - attributes?: CredentialPreviewAttributeOptions[], - linkedAttachments?: LinkedAttachment[] - ): { - attachments?: Attachment[] - previewAttributes?: CredentialPreviewAttribute[] - } { - if (!linkedAttachments && !attributes) { - return {} - } - - let previewAttributes = attributes?.map((attribute) => new CredentialPreviewAttribute(attribute)) ?? [] - let attachments: Attachment[] | undefined - - if (linkedAttachments) { - // there are linked attachments so transform into the attribute field of the CredentialPreview object for - // this proposal - previewAttributes = IndyCredentialUtils.createAndLinkAttachmentsToPreview(linkedAttachments, previewAttributes) - attachments = linkedAttachments.map((linkedAttachment) => linkedAttachment.attachment) - } - - return { attachments, previewAttributes } - } - - /** - * Returns an object of type {@link Attachment} for use in credential exchange messages. - * It looks up the correct format identifier and encodes the data as a base64 attachment. - * - * @param data The data to include in the attach object - * @param id the attach id from the formats component of the message - */ - private getFormatData(data: unknown, id: string): Attachment { - const attachment = new Attachment({ - id, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(data), - }), - }) - - return attachment - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts deleted file mode 100644 index 34042333e8..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialUtils.ts +++ /dev/null @@ -1,207 +0,0 @@ -import type { LinkedAttachment } from '../../../../utils/LinkedAttachment' -import type { CredValues, Schema } from 'indy-sdk' - -import BigNumber from 'bn.js' - -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { Hasher } from '../../../../utils' -import { encodeAttachment } from '../../../../utils/attachment' -import { Buffer } from '../../../../utils/buffer' -import { isBoolean, isNumber, isString } from '../../../../utils/type' -import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' - -export class IndyCredentialUtils { - /** - * Adds attribute(s) to the credential preview that is linked to the given attachment(s) - * - * @param attachments a list of the attachments that need to be linked to a credential - * @param preview the credential previews where the new linked credential has to be appended to - * - * @returns a modified version of the credential preview with the linked credentials - * */ - public static createAndLinkAttachmentsToPreview( - attachments: LinkedAttachment[], - previewAttributes: CredentialPreviewAttribute[] - ) { - const credentialPreviewAttributeNames = previewAttributes.map((attribute) => attribute.name) - const newPreviewAttributes = [...previewAttributes] - - attachments.forEach((linkedAttachment) => { - if (credentialPreviewAttributeNames.includes(linkedAttachment.attributeName)) { - throw new AriesFrameworkError( - `linkedAttachment ${linkedAttachment.attributeName} already exists in the preview` - ) - } else { - const credentialPreviewAttribute = new CredentialPreviewAttribute({ - name: linkedAttachment.attributeName, - mimeType: linkedAttachment.attachment.mimeType, - value: encodeAttachment(linkedAttachment.attachment), - }) - newPreviewAttributes.push(credentialPreviewAttribute) - } - }) - - return newPreviewAttributes - } - - /** - * Converts int value to string - * Converts string value: - * - hash with sha256, - * - convert to byte array and reverse it - * - convert it to BigInteger and return as a string - * @param attributes - * - * @returns CredValues - */ - public static convertAttributesToValues(attributes: CredentialPreviewAttribute[]): CredValues { - return attributes.reduce((credentialValues, attribute) => { - return { - [attribute.name]: { - raw: attribute.value, - encoded: IndyCredentialUtils.encode(attribute.value), - }, - ...credentialValues, - } - }, {}) - } - - /** - * Check whether the values of two credentials match (using {@link assertValuesMatch}) - * - * @returns a boolean whether the values are equal - * - */ - public static checkValuesMatch(firstValues: CredValues, secondValues: CredValues): boolean { - try { - this.assertValuesMatch(firstValues, secondValues) - return true - } catch { - return false - } - } - - /** - * Assert two credential values objects match. - * - * @param firstValues The first values object - * @param secondValues The second values object - * - * @throws If not all values match - */ - public static assertValuesMatch(firstValues: CredValues, secondValues: CredValues) { - const firstValuesKeys = Object.keys(firstValues) - const secondValuesKeys = Object.keys(secondValues) - - if (firstValuesKeys.length !== secondValuesKeys.length) { - throw new Error( - `Number of values in first entry (${firstValuesKeys.length}) does not match number of values in second entry (${secondValuesKeys.length})` - ) - } - - for (const key of firstValuesKeys) { - const firstValue = firstValues[key] - const secondValue = secondValues[key] - - if (!secondValue) { - throw new Error(`Second cred values object has no value for key '${key}'`) - } - - if (firstValue.encoded !== secondValue.encoded) { - throw new Error(`Encoded credential values for key '${key}' do not match`) - } - - if (firstValue.raw !== secondValue.raw) { - throw new Error(`Raw credential values for key '${key}' do not match`) - } - } - } - - /** - * Check whether the raw value matches the encoded version according to the encoding format described in Aries RFC 0037 - * Use this method to ensure the received proof (over the encoded) value is the same as the raw value of the data. - * - * @param raw - * @param encoded - * @returns Whether raw and encoded value match - * - * @see https://github.com/hyperledger/aries-framework-dotnet/blob/a18bef91e5b9e4a1892818df7408e2383c642dfa/src/Hyperledger.Aries/Utils/CredentialUtils.cs#L78-L89 - * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0037-present-proof/README.md#verifying-claims-of-indy-based-verifiable-credentials - */ - public static checkValidEncoding(raw: unknown, encoded: string) { - return encoded === IndyCredentialUtils.encode(raw) - } - - /** - * Encode value according to the encoding format described in Aries RFC 0036/0037 - * - * @param value - * @returns Encoded version of value - * - * @see https://github.com/hyperledger/aries-cloudagent-python/blob/0000f924a50b6ac5e6342bff90e64864672ee935/aries_cloudagent/messaging/util.py#L106-L136 - * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0037-present-proof/README.md#verifying-claims-of-indy-based-verifiable-credentials - * @see https://github.com/hyperledger/aries-rfcs/blob/be4ad0a6fb2823bb1fc109364c96f077d5d8dffa/features/0036-issue-credential/README.md#encoding-claims-for-indy-based-verifiable-credentials - */ - public static encode(value: unknown) { - const isEmpty = (value: unknown) => isString(value) && value === '' - - // If bool return bool as number string - if (isBoolean(value)) { - return Number(value).toString() - } - - // If value is int32 return as number string - if (isNumber(value) && this.isInt32(value)) { - return value.toString() - } - - // If value is an int32 number string return as number string - if ( - isString(value) && - !isEmpty(value) && - !isNaN(Number(value)) && - this.isNumeric(value) && - this.isInt32(Number(value)) - ) { - return Number(value).toString() - } - - if (isNumber(value)) { - value = value.toString() - } - - // If value is null we must use the string value 'None' - if (value === null || value === undefined) { - value = 'None' - } - - return new BigNumber(Hasher.hash(Buffer.from(value as string), 'sha2-256')).toString() - } - - public static checkAttributesMatch(schema: Schema, attributes: CredentialPreviewAttribute[]) { - const schemaAttributes = schema.attrNames - const credAttributes = attributes.map((a) => a.name) - - const difference = credAttributes - .filter((x) => !schemaAttributes.includes(x)) - .concat(schemaAttributes.filter((x) => !credAttributes.includes(x))) - - if (difference.length > 0) { - throw new AriesFrameworkError( - `The credential preview attributes do not match the schema attributes (difference is: ${difference}, needs: ${schemaAttributes})` - ) - } - } - - private static isInt32(number: number) { - const minI32 = -2147483648 - const maxI32 = 2147483647 - - // Check if number is integer and in range of int32 - return Number.isInteger(number) && number >= minI32 && number <= maxI32 - } - - private static isNumeric(value: string) { - return /^-?\d+$/.test(value) - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts b/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts deleted file mode 100644 index e89a849001..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/__tests__/IndyCredentialUtils.test.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAttribute' -import { IndyCredentialUtils } from '../IndyCredentialUtils' - -/** - * Sample test cases for encoding/decoding of verifiable credential claims - Aries RFCs 0036 and 0037 - * @see https://gist.github.com/swcurran/78e5a9e8d11236f003f6a6263c6619a6 - */ -const testEncodings: { [key: string]: { raw: string | number | boolean | null; encoded: string } } = { - address2: { - raw: '101 Wilson Lane', - encoded: '68086943237164982734333428280784300550565381723532936263016368251445461241953', - }, - zip: { - raw: '87121', - encoded: '87121', - }, - city: { - raw: 'SLC', - encoded: '101327353979588246869873249766058188995681113722618593621043638294296500696424', - }, - address1: { - raw: '101 Tela Lane', - encoded: '63690509275174663089934667471948380740244018358024875547775652380902762701972', - }, - state: { - raw: 'UT', - encoded: '93856629670657830351991220989031130499313559332549427637940645777813964461231', - }, - Empty: { - raw: '', - encoded: '102987336249554097029535212322581322789799900648198034993379397001115665086549', - }, - Null: { - raw: null, - encoded: '99769404535520360775991420569103450442789945655240760487761322098828903685777', - }, - 'bool True': { - raw: true, - encoded: '1', - }, - 'bool False': { - raw: false, - encoded: '0', - }, - 'str True': { - raw: 'True', - encoded: '27471875274925838976481193902417661171675582237244292940724984695988062543640', - }, - 'str False': { - raw: 'False', - encoded: '43710460381310391454089928988014746602980337898724813422905404670995938820350', - }, - 'max i32': { - raw: 2147483647, - encoded: '2147483647', - }, - 'max i32 + 1': { - raw: 2147483648, - encoded: '26221484005389514539852548961319751347124425277437769688639924217837557266135', - }, - 'min i32': { - raw: -2147483648, - encoded: '-2147483648', - }, - 'min i32 - 1': { - raw: -2147483649, - encoded: '68956915425095939579909400566452872085353864667122112803508671228696852865689', - }, - 'float 0.1': { - raw: 0.1, - encoded: '9382477430624249591204401974786823110077201914483282671737639310288175260432', - }, - 'str 0.1': { - raw: '0.1', - encoded: '9382477430624249591204401974786823110077201914483282671737639310288175260432', - }, - 'str 1.0': { - raw: '1.0', - encoded: '94532235908853478633102631881008651863941875830027892478278578250784387892726', - }, - 'str 1': { - raw: '1', - encoded: '1', - }, - 'leading zero number string': { - raw: '012345', - encoded: '12345', - }, - 'chr 0': { - raw: String.fromCharCode(0), - encoded: '49846369543417741186729467304575255505141344055555831574636310663216789168157', - }, - 'chr 1': { - raw: String.fromCharCode(1), - encoded: '34356466678672179216206944866734405838331831190171667647615530531663699592602', - }, - 'chr 2': { - raw: String.fromCharCode(2), - encoded: '99398763056634537812744552006896172984671876672520535998211840060697129507206', - }, -} - -describe('IndyCredentialUtils', () => { - describe('convertAttributesToValues', () => { - test('returns object with raw and encoded attributes', () => { - const attributes = [ - new CredentialPreviewAttribute({ - name: 'name', - mimeType: 'text/plain', - value: '101 Wilson Lane', - }), - new CredentialPreviewAttribute({ - name: 'age', - mimeType: 'text/plain', - value: '1234', - }), - ] - - expect(IndyCredentialUtils.convertAttributesToValues(attributes)).toEqual({ - name: { - raw: '101 Wilson Lane', - encoded: '68086943237164982734333428280784300550565381723532936263016368251445461241953', - }, - age: { raw: '1234', encoded: '1234' }, - }) - }) - }) - - describe('assertValuesMatch', () => { - test('does not throw if attributes match', () => { - const firstValues = { - name: { - raw: '101 Wilson Lane', - encoded: '68086943237164982734333428280784300550565381723532936263016368251445461241953', - }, - age: { raw: '1234', encoded: '1234' }, - } - const secondValues = { - name: { - raw: '101 Wilson Lane', - encoded: '68086943237164982734333428280784300550565381723532936263016368251445461241953', - }, - age: { raw: '1234', encoded: '1234' }, - } - - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).not.toThrow() - }) - - test('throws if number of values in the entries do not match', () => { - const firstValues = { - age: { raw: '1234', encoded: '1234' }, - } - const secondValues = { - name: { - raw: '101 Wilson Lane', - encoded: '68086943237164982734333428280784300550565381723532936263016368251445461241953', - }, - age: { raw: '1234', encoded: '1234' }, - } - - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).toThrow( - 'Number of values in first entry (1) does not match number of values in second entry (2)' - ) - }) - - test('throws if second value does not contain key from first value', () => { - const firstValues = { - name: { - raw: '101 Wilson Lane', - encoded: '68086943237164982734333428280784300550565381723532936263016368251445461241953', - }, - age: { raw: '1234', encoded: '1234' }, - } - const secondValues = { - anotherName: { - raw: '101 Wilson Lane', - encoded: '68086943237164982734333428280784300550565381723532936263016368251445461241953', - }, - age: { raw: '1234', encoded: '1234' }, - } - - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).toThrow( - "Second cred values object has no value for key 'name'" - ) - }) - - test('throws if encoded values do not match', () => { - const firstValues = { - age: { raw: '1234', encoded: '1234' }, - } - const secondValues = { - age: { raw: '1234', encoded: '12345' }, - } - - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).toThrow( - "Encoded credential values for key 'age' do not match" - ) - }) - - test('throws if raw values do not match', () => { - const firstValues = { - age: { raw: '1234', encoded: '1234' }, - } - const secondValues = { - age: { raw: '12345', encoded: '1234' }, - } - - expect(() => IndyCredentialUtils.assertValuesMatch(firstValues, secondValues)).toThrow( - "Raw credential values for key 'age' do not match" - ) - }) - }) - - describe('checkValidEncoding', () => { - // Formatted for test.each - const testEntries = Object.entries(testEncodings).map( - ([name, { raw, encoded }]) => [name, raw, encoded] as [string, string | number | boolean | null, string] - ) - - test.each(testEntries)('returns true for valid encoding %s', (_, raw, encoded) => { - expect(IndyCredentialUtils.checkValidEncoding(raw, encoded)).toEqual(true) - }) - }) -}) diff --git a/packages/core/src/modules/credentials/formats/indy/index.ts b/packages/core/src/modules/credentials/formats/indy/index.ts deleted file mode 100644 index f79b7ee9c2..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './models' -export * from './IndyCredentialFormatService' -export * from './IndyCredentialFormat' diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyCredPropose.ts b/packages/core/src/modules/credentials/formats/indy/models/IndyCredPropose.ts deleted file mode 100644 index 3ddfff7542..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyCredPropose.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { Expose } from 'class-transformer' -import { IsOptional, IsString } from 'class-validator' - -export interface IndyCredProposeOptions { - schemaIssuerDid?: string - schemaId?: string - schemaName?: string - schemaVersion?: string - credentialDefinitionId?: string - issuerDid?: string -} - -/** - * Class providing validation for the V2 credential proposal payload. - * - * The v1 message contains the properties directly in the message, which means they are - * validated using the class validator decorators. In v2 the attachments content is not transformed - * when transforming the message to a class instance so the content is not verified anymore, hence this - * class. - * - */ -export class IndyCredPropose { - public constructor(options: IndyCredProposeOptions) { - if (options) { - this.schemaIssuerDid = options.schemaIssuerDid - this.schemaId = options.schemaId - this.schemaName = options.schemaName - this.schemaVersion = options.schemaVersion - this.credentialDefinitionId = options.credentialDefinitionId - this.issuerDid = options.issuerDid - } - } - - /** - * Filter to request credential based on a particular Schema issuer DID. - */ - @Expose({ name: 'schema_issuer_did' }) - @IsString() - @IsOptional() - public schemaIssuerDid?: string - - /** - * Filter to request credential based on a particular Schema. - */ - @Expose({ name: 'schema_id' }) - @IsString() - @IsOptional() - public schemaId?: string - - /** - * Filter to request credential based on a schema name. - */ - @Expose({ name: 'schema_name' }) - @IsString() - @IsOptional() - public schemaName?: string - - /** - * Filter to request credential based on a schema version. - */ - @Expose({ name: 'schema_version' }) - @IsString() - @IsOptional() - public schemaVersion?: string - - /** - * Filter to request credential based on a particular Credential Definition. - */ - @Expose({ name: 'cred_def_id' }) - @IsString() - @IsOptional() - public credentialDefinitionId?: string - - /** - * Filter to request a credential issued by the owner of a particular DID. - */ - @Expose({ name: 'issuer_did' }) - @IsString() - @IsOptional() - public issuerDid?: string -} diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyCredential.ts b/packages/core/src/modules/credentials/formats/indy/models/IndyCredential.ts deleted file mode 100644 index 7c1addefb4..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyCredential.ts +++ /dev/null @@ -1,34 +0,0 @@ -import type * as Indy from 'indy-sdk' - -import { Expose, Type } from 'class-transformer' -import { IsInstance, IsOptional, ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils/JsonTransformer' - -import { IndyCredentialInfo } from './IndyCredentialInfo' -import { IndyRevocationInterval } from './IndyRevocationInterval' - -export class IndyCredential { - public constructor(options: IndyCredential) { - if (options) { - this.credentialInfo = options.credentialInfo - this.interval = options.interval - } - } - - @Expose({ name: 'cred_info' }) - @Type(() => IndyCredentialInfo) - @ValidateNested() - @IsInstance(IndyCredentialInfo) - public credentialInfo!: IndyCredentialInfo - - @IsOptional() - @Type(() => IndyRevocationInterval) - @ValidateNested() - @IsInstance(IndyRevocationInterval) - public interval?: IndyRevocationInterval - - public toJSON(): Indy.IndyCredential { - return JsonTransformer.toJSON(this) as unknown as Indy.IndyCredential - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialInfo.ts b/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialInfo.ts deleted file mode 100644 index 9edd269157..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialInfo.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { IndyCredentialInfo as IndySDKCredentialInfo } from 'indy-sdk' - -import { Expose } from 'class-transformer' -import { IsOptional, IsString } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils' - -export interface IndyCredentialInfoOptions { - referent: string - attributes: Record - schemaId: string - credentialDefinitionId: string - revocationRegistryId?: string - credentialRevocationId?: string -} - -export class IndyCredentialInfo { - public constructor(options: IndyCredentialInfoOptions) { - if (options) { - this.referent = options.referent - this.attributes = options.attributes - this.schemaId = options.schemaId - this.credentialDefinitionId = options.credentialDefinitionId - this.revocationRegistryId = options.revocationRegistryId - this.credentialRevocationId = options.credentialRevocationId - } - } - - /** - * Credential ID in the wallet - */ - @IsString() - public referent!: string - - @Expose({ name: 'attrs' }) - @IsString({ each: true }) - public attributes!: Record - - @Expose({ name: 'schema_id' }) - @IsString() - public schemaId!: string - - @Expose({ name: 'cred_def_id' }) - @IsString() - public credentialDefinitionId!: string - - @Expose({ name: 'rev_reg_id' }) - @IsString() - @IsOptional() - public revocationRegistryId?: string - - @Expose({ name: 'cred_rev_id' }) - @IsString() - @IsOptional() - public credentialRevocationId?: string - - public toJSON(): IndySDKCredentialInfo { - return JsonTransformer.toJSON(this) as unknown as IndySDKCredentialInfo - } -} diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialView.ts b/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialView.ts deleted file mode 100644 index 05f18c6e9c..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyCredentialView.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { Attachment } from '../../../../../decorators/attachment/Attachment' - -export interface IndyCredentialViewModel { - metadata?: IndyCredentialViewMetadata | null - claims: Record - attachments?: Attachment[] -} - -export interface IndyCredentialViewMetadata { - credentialDefinitionId?: string - schemaId?: string -} - -export class IndyCredentialView { - public constructor(options: IndyCredentialViewModel) { - this.metadata = options.metadata ?? {} - this.claims = options.claims - this.attachments = options.attachments - } - - public metadata: IndyCredentialViewMetadata - public claims: Record - public attachments?: Attachment[] -} diff --git a/packages/core/src/modules/credentials/formats/indy/models/IndyRevocationInterval.ts b/packages/core/src/modules/credentials/formats/indy/models/IndyRevocationInterval.ts deleted file mode 100644 index 6057153aaa..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/IndyRevocationInterval.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { IsInt, IsOptional } from 'class-validator' - -export class IndyRevocationInterval { - public constructor(options: { from?: number; to?: number }) { - if (options) { - this.from = options.from - this.to = options.to - } - } - - @IsInt() - @IsOptional() - public from?: number - - @IsInt() - @IsOptional() - public to?: number -} diff --git a/packages/core/src/modules/credentials/formats/indy/models/__tests__/IndyCredentialView.test.ts b/packages/core/src/modules/credentials/formats/indy/models/__tests__/IndyCredentialView.test.ts deleted file mode 100644 index 840099d41d..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/__tests__/IndyCredentialView.test.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { IndyCredentialView } from '../IndyCredentialView' - -describe('CredentialInfo', () => { - it('should return the correct property values', () => { - const claims = { - name: 'Timo', - date_of_birth: '1998-07-29', - 'country-of-residence': 'The Netherlands', - 'street name': 'Test street', - age: '22', - } - const metadata = { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - schemaId: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - } - const credentialInfo = new IndyCredentialView({ - claims, - metadata, - }) - - expect(credentialInfo.claims).toEqual(claims) - expect(credentialInfo.metadata).toEqual(metadata) - }) -}) diff --git a/packages/core/src/modules/credentials/formats/indy/models/index.ts b/packages/core/src/modules/credentials/formats/indy/models/index.ts deleted file mode 100644 index 8f63220f10..0000000000 --- a/packages/core/src/modules/credentials/formats/indy/models/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './IndyCredential' -export * from './IndyCredentialInfo' -export * from './IndyRevocationInterval' -export * from './IndyCredentialView' -export * from './IndyCredPropose' diff --git a/packages/core/src/modules/credentials/protocol/index.ts b/packages/core/src/modules/credentials/protocol/index.ts index 6433655022..394b0e5b41 100644 --- a/packages/core/src/modules/credentials/protocol/index.ts +++ b/packages/core/src/modules/credentials/protocol/index.ts @@ -1,3 +1,6 @@ -export * from './v1' export * from './v2' export * from './revocation-notification' +import * as CredentialProtocolOptions from './CredentialProtocolOptions' + +export { CredentialProtocol } from './CredentialProtocol' +export { CredentialProtocolOptions } diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index 0c62263961..974cef2c84 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -6,7 +6,11 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { prepareForIssuance, waitForCredentialRecordSubject, getAgentOptions } from '../../../../../../tests/helpers' +import { + prepareForIndyIssuance, + waitForCredentialRecordSubject, + getAgentOptions, +} from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { CredentialEventTypes } from '../../../CredentialEvents' @@ -53,7 +57,7 @@ describe('V2 Connectionless Credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age']) + const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age']) credentialDefinitionId = definition.id faberReplay = new ReplaySubject() diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts index c3b73fd6ef..10c0ae5827 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts @@ -1,11 +1,8 @@ -import type { Agent } from '../../../../../agent/Agent' +import type { ReplaySubject } from 'rxjs' import type { ConnectionRecord } from '../../../../connections' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { IndyCredPropose } from '../../../formats/indy/models/IndyCredPropose' -import type { ReplaySubject } from 'rxjs' import { - issueCredential, setupCredentialTests, waitForCredentialRecord, waitForCredentialRecordSubject, @@ -13,17 +10,25 @@ import { import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils' -import { IndyHolderService } from '../../../../indy/services/IndyHolderService' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { + V2CredentialPreview, V2IssueCredentialMessage, + V2OfferCredentialMessage, V2ProposeCredentialMessage, V2RequestCredentialMessage, - V2CredentialPreview, - V2OfferCredentialMessage, } from '../messages' +import { AnonCredsHolderService, AnonCredsHolderServiceSymbol } from '../../../../../../../anoncreds/src' +import { + AnonCredsAgent, + issueLegacyAnonCredsCredential, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' + +// TODO: what to export from anoncreds root package? +import { LegacyIndyProposeCredentialFormat } from '../../../../../../../anoncreds/src/formats/LegacyIndyCredentialFormat' + const credentialPreview = V2CredentialPreview.fromRecord({ name: 'John', age: '99', @@ -32,8 +37,8 @@ const credentialPreview = V2CredentialPreview.fromRecord({ }) describe('v2 credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: AnonCredsAgent + let aliceAgent: AnonCredsAgent let credDefId: string let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord @@ -42,7 +47,7 @@ describe('v2 credentials', () => { let faberReplay: ReplaySubject let aliceReplay: ReplaySubject - let credPropose: IndyCredPropose + let credPropose: LegacyIndyProposeCredentialFormat const newCredentialPreview = V2CredentialPreview.fromRecord({ name: 'John', @@ -216,7 +221,7 @@ describe('v2 credentials', () => { }) test('Faber issues credential which is then deleted from Alice`s wallet', async () => { - const { holderCredential } = await issueCredential({ + const { holderCredentialExchangeRecord, issuerCredentialExchangeRecord } = await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, issuerConnectionId: faberConnection.id, holderAgent: aliceAgent, @@ -227,23 +232,23 @@ describe('v2 credentials', () => { }) // test that delete credential removes from both repository and wallet - // latter is tested by spying on holder service (Indy) to + // latter is tested by spying on holder service to // see if deleteCredential is called - const holderService = aliceAgent.dependencyManager.resolve(IndyHolderService) + const holderService = aliceAgent.dependencyManager.resolve(AnonCredsHolderServiceSymbol) const deleteCredentialSpy = jest.spyOn(holderService, 'deleteCredential') - await aliceAgent.credentials.deleteById(holderCredential.id, { + await aliceAgent.credentials.deleteById(holderCredentialExchangeRecord.id, { deleteAssociatedCredentials: true, deleteAssociatedDidCommMessages: true, }) expect(deleteCredentialSpy).toHaveBeenNthCalledWith( 1, aliceAgent.context, - holderCredential.credentials[0].credentialRecordId + holderCredentialExchangeRecord.credentials[0].credentialRecordId ) - return expect(aliceAgent.credentials.getById(holderCredential.id)).rejects.toThrowError( - `CredentialRecord: record with id ${holderCredential.id} not found.` + return expect(aliceAgent.credentials.getById(holderCredentialExchangeRecord.id)).rejects.toThrowError( + `CredentialRecord: record with id ${holderCredentialExchangeRecord.id} not found.` ) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index 2e40d6234f..55cb8c273c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -8,7 +8,11 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, prepareForIssuance, waitForCredentialRecordSubject } from '../../../../../../tests/helpers' +import { + getAgentOptions, + prepareForIndyIssuance, + waitForCredentialRecordSubject, +} from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { InjectionSymbols } from '../../../../../constants' @@ -93,7 +97,7 @@ describe('credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - await prepareForIssuance(faberAgent, ['name', 'age']) + await prepareForIndyIssuance(faberAgent, ['name', 'age']) faberReplay = new ReplaySubject() aliceReplay = new ReplaySubject() diff --git a/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts index 290865b83a..edcf443cdb 100644 --- a/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts +++ b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts @@ -10,7 +10,6 @@ import { Attachment } from '../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../error' import { BaseRecord } from '../../../storage/BaseRecord' import { uuid } from '../../../utils/uuid' -import { IndyCredentialView } from '../formats/indy/models/IndyCredentialView' import { CredentialPreviewAttribute } from '../models/CredentialPreviewAttribute' import { CredentialMetadataKeys } from './CredentialMetadataTypes' @@ -106,24 +105,6 @@ export class CredentialExchangeRecord extends BaseRecord< } } - public getCredentialInfo(): IndyCredentialView | null { - if (!this.credentialAttributes) return null - - const claims = this.credentialAttributes.reduce( - (accumulator, current) => ({ - ...accumulator, - [current.name]: current.value, - }), - {} - ) - - return new IndyCredentialView({ - claims, - attachments: this.linkedAttachments, - metadata: this.metadata.data, - }) - } - public assertProtocolVersion(version: string) { if (this.protocolVersion != version) { throw new AriesFrameworkError( diff --git a/packages/core/src/modules/credentials/repository/__tests__/CredentialExchangeRecord.test.ts b/packages/core/src/modules/credentials/repository/__tests__/CredentialExchangeRecord.test.ts deleted file mode 100644 index 688f21bad1..0000000000 --- a/packages/core/src/modules/credentials/repository/__tests__/CredentialExchangeRecord.test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' -import { CredentialState } from '../../models/CredentialState' -import { CredentialExchangeRecord } from '../CredentialExchangeRecord' -import { CredentialMetadataKeys } from '../CredentialMetadataTypes' - -describe('CredentialExchangeRecord', () => { - describe('getCredentialInfo()', () => { - test('creates credential info object from credential record data', () => { - const credentialRecord = new CredentialExchangeRecord({ - connectionId: '28790bfe-1345-4c64-b21a-7d98982b3894', - threadId: 'threadId', - state: CredentialState.Done, - credentialAttributes: [ - new CredentialPreviewAttribute({ - name: 'age', - value: '25', - }), - ], - protocolVersion: 'v1', - }) - - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - schemaId: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - }) - - const credentialInfo = credentialRecord.getCredentialInfo() - - expect(credentialInfo).toEqual({ - claims: { - age: '25', - }, - metadata: { - '_internal/indyCredential': { - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - schemaId: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - }, - }, - }) - }) - }) -}) diff --git a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts index 9599d06c92..7fd599fa45 100644 --- a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts @@ -1,31 +1,14 @@ import type { KeyDidCreateOptions } from '../methods/key/KeyDidRegistrar' import type { PeerDidNumAlgo0CreateOptions } from '../methods/peer/PeerDidRegistrar' -import type { SovDidCreateOptions } from '../methods/sov/IndySdkSovDidRegistrar' -import type { Wallet } from '@aries-framework/core' -import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' - -import { genesisPath, getAgentOptions } from '../../../../tests/helpers' +import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { KeyType } from '../../../crypto' -import { TypedArrayEncoder } from '../../../utils' -import { indyDidFromPublicKeyBase58 } from '../../../utils/did' - -import { InjectionSymbols, JsonTransformer } from '@aries-framework/core' - import { PeerDidNumAlgo } from '../methods/peer/didPeer' -const agentOptions = getAgentOptions('Faber Dids Registrar', { - indyLedgers: [ - { - id: `localhost`, - isProduction: false, - genesisPath, - indyNamespace: 'localhost', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, - ], -}) +import { JsonTransformer } from '@aries-framework/core' + +const agentOptions = getAgentOptions('Faber Dids Registrar') describe('dids', () => { let agent: Agent @@ -164,106 +147,4 @@ describe('dids', () => { }, }) }) - - it('should create a did:sov did', async () => { - // Generate a seed and the indy did. This allows us to create a new did every time - // but still check if the created output document is as expected. - const seed = Array(32 + 1) - .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) - .slice(0, 32) - - const publicKeyEd25519 = generateKeyPairFromSeed(TypedArrayEncoder.fromString(seed)).publicKey - const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(publicKeyEd25519)) - const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyEd25519) - const indyDid = indyDidFromPublicKeyBase58(ed25519PublicKeyBase58) - - const wallet = agent.dependencyManager.resolve(InjectionSymbols.Wallet) - // eslint-disable-next-line @typescript-eslint/no-non-null-asserted-optional-chain, @typescript-eslint/no-non-null-assertion - const submitterDid = `did:sov:${wallet.publicDid?.did!}` - - const did = await agent.dids.create({ - method: 'sov', - options: { - submitterDid, - alias: 'Alias', - endpoints: { - endpoint: 'https://example.com/endpoint', - types: ['DIDComm', 'did-communication', 'endpoint'], - routingKeys: ['a-routing-key'], - }, - }, - secret: { - seed, - }, - }) - - expect(JsonTransformer.toJSON(did)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: `did:indy:localhost:${indyDid}`, - }, - didRegistrationMetadata: { - didIndyNamespace: 'localhost', - }, - didState: { - state: 'finished', - did: `did:sov:${indyDid}`, - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - 'https://didcomm.org/messaging/contexts/v2', - ], - alsoKnownAs: undefined, - controller: undefined, - verificationMethod: [ - { - id: `did:sov:${indyDid}#key-1`, - type: 'Ed25519VerificationKey2018', - controller: `did:sov:${indyDid}`, - publicKeyBase58: ed25519PublicKeyBase58, - }, - { - id: `did:sov:${indyDid}#key-agreement-1`, - type: 'X25519KeyAgreementKey2019', - controller: `did:sov:${indyDid}`, - publicKeyBase58: x25519PublicKeyBase58, - }, - ], - service: [ - { - id: `did:sov:${indyDid}#endpoint`, - serviceEndpoint: 'https://example.com/endpoint', - type: 'endpoint', - }, - { - accept: ['didcomm/aip2;env=rfc19'], - id: `did:sov:${indyDid}#did-communication`, - priority: 0, - recipientKeys: [`did:sov:${indyDid}#key-agreement-1`], - routingKeys: ['a-routing-key'], - serviceEndpoint: 'https://example.com/endpoint', - type: 'did-communication', - }, - { - accept: ['didcomm/v2'], - id: `did:sov:${indyDid}#didcomm-1`, - routingKeys: ['a-routing-key'], - serviceEndpoint: 'https://example.com/endpoint', - type: 'DIDComm', - }, - ], - authentication: [`did:sov:${indyDid}#key-1`], - assertionMethod: [`did:sov:${indyDid}#key-1`], - keyAgreement: [`did:sov:${indyDid}#key-agreement-1`], - capabilityInvocation: undefined, - capabilityDelegation: undefined, - id: `did:sov:${indyDid}`, - }, - secret: { - seed, - }, - }, - }) - }) }) diff --git a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts index 09d64b2b70..8cdf425a48 100644 --- a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts @@ -1,10 +1,6 @@ -import type { SovDidCreateOptions } from '../methods' - import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' -import { AriesFrameworkError } from '../../../error' import { JsonTransformer } from '../../../utils' -import { sleep } from '../../../utils/sleep' describe('dids', () => { let agent: Agent @@ -19,64 +15,6 @@ describe('dids', () => { await agent.wallet.delete() }) - it('should resolve a did:sov did', async () => { - const publicDid = agent.publicDid?.did - - if (!publicDid) throw new Error('Agent has no public did') - - const createResult = await agent.dids.create({ - method: 'sov', - options: { - submitterDid: `did:sov:${publicDid}`, - alias: 'Alias', - role: 'TRUSTEE', - }, - }) - - // Terrible, but the did can't be immediately resolved, so we need to wait a bit - await sleep(1000) - - if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') - const didResult = await agent.dids.resolve(createResult.didState.did) - - expect(JsonTransformer.toJSON(didResult)).toMatchObject({ - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - ], - id: createResult.didState.did, - alsoKnownAs: undefined, - controller: undefined, - verificationMethod: [ - { - type: 'Ed25519VerificationKey2018', - controller: createResult.didState.did, - id: `${createResult.didState.did}#key-1`, - publicKeyBase58: expect.any(String), - }, - { - controller: createResult.didState.did, - type: 'X25519KeyAgreementKey2019', - id: `${createResult.didState.did}#key-agreement-1`, - publicKeyBase58: expect.any(String), - }, - ], - capabilityDelegation: undefined, - capabilityInvocation: undefined, - authentication: [`${createResult.didState.did}#key-1`], - assertionMethod: [`${createResult.didState.did}#key-1`], - keyAgreement: [`${createResult.didState.did}#key-agreement-1`], - service: undefined, - }, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) - }) - it('should resolve a did:key did', async () => { const did = await agent.dids.resolve('did:key:z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') diff --git a/packages/core/src/modules/dids/methods/index.ts b/packages/core/src/modules/dids/methods/index.ts index ebacc7f2c2..12f78247af 100644 --- a/packages/core/src/modules/dids/methods/index.ts +++ b/packages/core/src/modules/dids/methods/index.ts @@ -1,4 +1,3 @@ export * from './key' export * from './peer' -export * from './sov' export * from './web' diff --git a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts deleted file mode 100644 index 763f97b622..0000000000 --- a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts +++ /dev/null @@ -1,246 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { IndyEndpointAttrib, IndyPool } from '../../../ledger' -import type { DidRegistrar } from '../../domain/DidRegistrar' -import type { DidCreateOptions, DidCreateResult, DidDeactivateResult, DidUpdateResult } from '../../types' -import type * as Indy from 'indy-sdk' - -import { IndySdkError } from '../../../../error' -import { injectable } from '../../../../plugins' -import { isIndyError } from '../../../../utils/indyError' -import { assertIndyWallet } from '../../../../wallet/util/assertIndyWallet' -import { IndyPoolService } from '../../../ledger' -import { DidDocumentRole } from '../../domain/DidDocumentRole' -import { DidRecord, DidRepository } from '../../repository' - -import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' - -@injectable() -export class IndySdkSovDidRegistrar implements DidRegistrar { - public readonly supportedMethods = ['sov'] - - public async create(agentContext: AgentContext, options: SovDidCreateOptions): Promise { - const indy = agentContext.config.agentDependencies.indy - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - const didRepository = agentContext.dependencyManager.resolve(DidRepository) - - const { alias, role, submitterDid, indyNamespace } = options.options - const seed = options.secret?.seed - - if (seed && (typeof seed !== 'string' || seed.length !== 32)) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid seed provided', - }, - } - } - - if (!submitterDid.startsWith('did:sov:')) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Submitter did must be a valid did:sov did', - }, - } - } - - try { - // NOTE: we need to use the createAndStoreMyDid method from indy to create the did - // If we just create a key and handle the creating of the did ourselves, indy will throw a - // WalletItemNotFound when it needs to sign ledger transactions using this did. This means we need - // to rely directly on the indy SDK, as we don't want to expose a createDid method just for. - // FIXME: once askar/indy-vdr is supported we need to adjust this to work with both indy-sdk and askar - assertIndyWallet(agentContext.wallet) - const [unqualifiedIndyDid, verkey] = await indy.createAndStoreMyDid(agentContext.wallet.handle, { - seed, - }) - - const qualifiedSovDid = `did:sov:${unqualifiedIndyDid}` - const unqualifiedSubmitterDid = submitterDid.replace('did:sov:', '') - - // TODO: it should be possible to pass the pool used for writing to the indy ledger service. - // The easiest way to do this would be to make the submitterDid a fully qualified did, including the indy namespace. - const pool = indyPoolService.getPoolForNamespace(indyNamespace) - await this.registerPublicDid(agentContext, unqualifiedSubmitterDid, unqualifiedIndyDid, verkey, alias, pool, role) - - // Create did document - const didDocumentBuilder = sovDidDocumentFromDid(qualifiedSovDid, verkey) - - // Add services if endpoints object was passed. - if (options.options.endpoints) { - await this.setEndpointsForDid(agentContext, unqualifiedIndyDid, options.options.endpoints, pool) - addServicesFromEndpointsAttrib( - didDocumentBuilder, - qualifiedSovDid, - options.options.endpoints, - `${qualifiedSovDid}#key-agreement-1` - ) - } - - // Build did document. - const didDocument = didDocumentBuilder.build() - - const didIndyNamespace = pool.config.indyNamespace - const qualifiedIndyDid = `did:indy:${didIndyNamespace}:${unqualifiedIndyDid}` - - // Save the did so we know we created it and can issue with it - const didRecord = new DidRecord({ - did: qualifiedSovDid, - role: DidDocumentRole.Created, - tags: { - recipientKeyFingerprints: didDocument.recipientKeys.map((key) => key.fingerprint), - qualifiedIndyDid, - }, - }) - await didRepository.save(agentContext, didRecord) - - return { - didDocumentMetadata: { - qualifiedIndyDid, - }, - didRegistrationMetadata: { - didIndyNamespace, - }, - didState: { - state: 'finished', - did: qualifiedSovDid, - didDocument, - secret: { - // FIXME: the uni-registrar creates the seed in the registrar method - // if it doesn't exist so the seed can always be returned. Currently - // we can only return it if the seed was passed in by the user. Once - // we have a secure method for generating seeds we should use the same - // approach - seed: options.secret?.seed, - }, - }, - } - } catch (error) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `unknownError: ${error.message}`, - }, - } - } - } - - public async update(): Promise { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: updating did:sov not implemented yet`, - }, - } - } - - public async deactivate(): Promise { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: deactivating did:sov not implemented yet`, - }, - } - } - - public async registerPublicDid( - agentContext: AgentContext, - submitterDid: string, - targetDid: string, - verkey: string, - alias: string, - pool: IndyPool, - role?: Indy.NymRole - ) { - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - const indy = agentContext.config.agentDependencies.indy - - try { - agentContext.config.logger.debug(`Register public did '${targetDid}' on ledger '${pool.id}'`) - - const request = await indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) - - const response = await indyPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) - - agentContext.config.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { - response, - }) - - return targetDid - } catch (error) { - agentContext.config.logger.error(`Error registering public did '${targetDid}' on ledger '${pool.id}'`, { - error, - submitterDid, - targetDid, - verkey, - alias, - role, - pool: pool.id, - }) - - throw error - } - } - - public async setEndpointsForDid( - agentContext: AgentContext, - did: string, - endpoints: IndyEndpointAttrib, - pool: IndyPool - ): Promise { - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - const indy = agentContext.config.agentDependencies.indy - - try { - agentContext.config.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) - - const request = await indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) - - const response = await indyPoolService.submitWriteRequest(agentContext, pool, request, did) - agentContext.config.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { - response, - endpoints, - }) - } catch (error) { - agentContext.config.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.id}'`, { - error, - did, - endpoints, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} - -export interface SovDidCreateOptions extends DidCreateOptions { - method: 'sov' - did?: undefined - // As did:sov is so limited, we require everything needed to construct the did document to be passed - // through the options object. Once we support did:indy we can allow the didDocument property. - didDocument?: never - options: { - alias: string - role?: Indy.NymRole - endpoints?: IndyEndpointAttrib - indyNamespace?: string - submitterDid: string - } - secret?: { - seed?: string - } -} - -// Update and Deactivate not supported for did:sov -export type IndyDidUpdateOptions = never -export type IndyDidDeactivateOptions = never diff --git a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts b/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts deleted file mode 100644 index bae98c5587..0000000000 --- a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidResolver.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { IndyEndpointAttrib } from '../../../ledger' -import type { DidResolver } from '../../domain/DidResolver' -import type { DidResolutionResult, ParsedDid } from '../../types' - -import { IndySdkError } from '../../../../error' -import { injectable } from '../../../../plugins' -import { isIndyError } from '../../../../utils/indyError' -import { IndyPoolService } from '../../../ledger' - -import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './util' - -@injectable() -export class IndySdkSovDidResolver implements DidResolver { - public readonly supportedMethods = ['sov'] - - public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { - const didDocumentMetadata = {} - - try { - const nym = await this.getPublicDid(agentContext, parsed.id) - const endpoints = await this.getEndpointsForDid(agentContext, parsed.id) - - const keyAgreementId = `${parsed.did}#key-agreement-1` - const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) - addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) - - return { - didDocument: builder.build(), - didDocumentMetadata, - didResolutionMetadata: { contentType: 'application/did+ld+json' }, - } - } catch (error) { - return { - didDocument: null, - didDocumentMetadata, - didResolutionMetadata: { - error: 'notFound', - message: `resolver_error: Unable to resolve did '${did}': ${error}`, - }, - } - } - } - - private async getPublicDid(agentContext: AgentContext, did: string) { - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - - // Getting the pool for a did also retrieves the DID. We can just use that - const { did: didResponse } = await indyPoolService.getPoolForDid(agentContext, did) - - return didResponse - } - - private async getEndpointsForDid(agentContext: AgentContext, did: string) { - const indyPoolService = agentContext.dependencyManager.resolve(IndyPoolService) - const indy = agentContext.config.agentDependencies.indy - - const { pool } = await indyPoolService.getPoolForDid(agentContext, did) - - try { - agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) - - const request = await indy.buildGetAttribRequest(null, did, 'endpoint', null, null) - - agentContext.config.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) - const response = await indyPoolService.submitReadRequest(pool, request) - - if (!response.result.data) return {} - - const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib - agentContext.config.logger.debug( - `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.id}'`, - { - response, - endpoints, - } - ) - - return endpoints ?? {} - } catch (error) { - agentContext.config.logger.error(`Error retrieving endpoints for did '${did}' from ledger '${pool.id}'`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts deleted file mode 100644 index 761a2956b6..0000000000 --- a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts +++ /dev/null @@ -1,373 +0,0 @@ -import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { Wallet } from '../../../../../wallet' -import type { IndyPool } from '../../../../ledger' -import type * as Indy from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../../../tests/helpers' -import { SigningProviderRegistry } from '../../../../../crypto/signing-provider' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IndyWallet } from '../../../../../wallet/IndyWallet' -import { IndyPoolService } from '../../../../ledger/services/IndyPoolService' -import { DidDocumentRole } from '../../../domain/DidDocumentRole' -import { DidRepository } from '../../../repository/DidRepository' -import { IndySdkSovDidRegistrar } from '../IndySdkSovDidRegistrar' - -jest.mock('../../../repository/DidRepository') -const DidRepositoryMock = DidRepository as jest.Mock - -jest.mock('../../../../ledger/services/IndyPoolService') -const IndyPoolServiceMock = IndyPoolService as jest.Mock -const indyPoolServiceMock = new IndyPoolServiceMock() -mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ - config: { id: 'pool1', indyNamespace: 'pool1' }, -} as IndyPool) - -const agentConfig = getAgentConfig('IndySdkSovDidRegistrar') -const createDidMock = jest.fn(async () => ['R1xKJw17sUoXhejEpugMYJ', 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu']) - -const wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) -mockProperty(wallet, 'handle', 10) - -const didRepositoryMock = new DidRepositoryMock() -const agentContext = getAgentContext({ - wallet, - registerInstances: [ - [DidRepository, didRepositoryMock], - [IndyPoolService, indyPoolServiceMock], - ], - agentConfig: { - ...agentConfig, - agentDependencies: { - ...agentConfig.agentDependencies, - indy: { createAndStoreMyDid: createDidMock } as unknown as typeof Indy, - }, - } as AgentConfig, -}) - -const indySdkSovDidRegistrar = new IndySdkSovDidRegistrar() - -describe('DidRegistrar', () => { - describe('IndySdkSovDidRegistrar', () => { - afterEach(() => { - jest.clearAllMocks() - }) - - it('should return an error state if an invalid seed is provided', async () => { - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - - options: { - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - alias: 'Hello', - }, - secret: { - seed: 'invalid', - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid seed provided', - }, - }) - }) - - it('should return an error state if the wallet is not an indy wallet', async () => { - const agentContext = getAgentContext({ - wallet: {} as unknown as Wallet, - agentConfig, - }) - - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - - options: { - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - alias: 'Hello', - }, - secret: { - seed: '12345678901234567890123456789012', - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'unknownError: Expected wallet to be instance of IndyWallet, found Object', - }, - }) - }) - - it('should return an error state if the submitter did is not qualified with did:sov', async () => { - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', - alias: 'Hello', - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Submitter did must be a valid did:sov did', - }, - }) - }) - - it('should correctly create a did:sov document without services', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) - - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - role: 'STEWARD', - }, - secret: { - seed, - }, - }) - - expect(registerPublicDidSpy).toHaveBeenCalledWith( - agentContext, - // Unqualified submitter did - 'BzCbsNYhMrjHiqZDTUASHg', - // Unqualified created indy did - 'R1xKJw17sUoXhejEpugMYJ', - // Verkey - 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - // Alias - 'Hello', - // Pool - { config: { id: 'pool1', indyNamespace: 'pool1' } }, - // Role - 'STEWARD' - ) - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didRegistrationMetadata: { - didIndyNamespace: 'pool1', - }, - didState: { - state: 'finished', - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - ], - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - verificationMethod: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', - type: 'Ed25519VerificationKey2018', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', - type: 'X25519KeyAgreementKey2019', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', - }, - ], - authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - }, - secret: { - seed, - }, - }, - }) - }) - - it('should correctly create a did:sov document with services', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) - - const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') - setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) - - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - role: 'STEWARD', - endpoints: { - endpoint: 'https://example.com/endpoint', - routingKeys: ['key-1'], - types: ['DIDComm', 'did-communication', 'endpoint'], - }, - }, - secret: { - seed, - }, - }) - - expect(registerPublicDidSpy).toHaveBeenCalledWith( - agentContext, - // Unqualified submitter did - 'BzCbsNYhMrjHiqZDTUASHg', - // Unqualified created indy did - 'R1xKJw17sUoXhejEpugMYJ', - // Verkey - 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - // Alias - 'Hello', - // Pool - { config: { id: 'pool1', indyNamespace: 'pool1' } }, - // Role - 'STEWARD' - ) - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didRegistrationMetadata: { - didIndyNamespace: 'pool1', - }, - didState: { - state: 'finished', - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - 'https://didcomm.org/messaging/contexts/v2', - ], - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - verificationMethod: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', - type: 'Ed25519VerificationKey2018', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', - type: 'X25519KeyAgreementKey2019', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', - }, - ], - service: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint', - serviceEndpoint: 'https://example.com/endpoint', - type: 'endpoint', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#did-communication', - serviceEndpoint: 'https://example.com/endpoint', - type: 'did-communication', - priority: 0, - recipientKeys: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - routingKeys: ['key-1'], - accept: ['didcomm/aip2;env=rfc19'], - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#didcomm-1', - serviceEndpoint: 'https://example.com/endpoint', - type: 'DIDComm', - routingKeys: ['key-1'], - accept: ['didcomm/v2'], - }, - ], - authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - }, - secret: { - seed, - }, - }, - }) - }) - - it('should store the did document', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('did')) - - const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') - setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) - - await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - role: 'STEWARD', - endpoints: { - endpoint: 'https://example.com/endpoint', - routingKeys: ['key-1'], - types: ['DIDComm', 'did-communication', 'endpoint'], - }, - }, - secret: { - seed, - }, - }) - - expect(didRepositoryMock.save).toHaveBeenCalledTimes(1) - const [, didRecord] = mockFunction(didRepositoryMock.save).mock.calls[0] - - expect(didRecord).toMatchObject({ - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - role: DidDocumentRole.Created, - _tags: { - recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didDocument: undefined, - }) - }) - - it('should return an error state when calling update', async () => { - const result = await indySdkSovDidRegistrar.update() - - expect(result).toEqual({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: updating did:sov not implemented yet`, - }, - }) - }) - - it('should return an error state when calling deactivate', async () => { - const result = await indySdkSovDidRegistrar.deactivate() - - expect(result).toEqual({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: deactivating did:sov not implemented yet`, - }, - }) - }) - }) -}) diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts deleted file mode 100644 index 6d082792f0..0000000000 --- a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidResolver.test.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { IndyPool } from '../../../../ledger' -import type { IndyEndpointAttrib } from '../../../../ledger/services/IndyLedgerService' -import type { GetNymResponse } from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../../../tests/helpers' -import { SigningProviderRegistry } from '../../../../../crypto/signing-provider' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IndyWallet } from '../../../../../wallet/IndyWallet' -import { IndyPoolService } from '../../../../ledger/services/IndyPoolService' -import didSovR1xKJw17sUoXhejEpugMYJFixture from '../../../__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' -import didSovWJz9mHyW9BZksioQnRsrAoFixture from '../../../__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' -import { parseDid } from '../../../domain/parse' -import { IndySdkSovDidResolver } from '../IndySdkSovDidResolver' - -jest.mock('../../../../ledger/services/IndyPoolService') -const IndyPoolServiceMock = IndyPoolService as jest.Mock -const indyPoolServiceMock = new IndyPoolServiceMock() -mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ - config: { id: 'pool1', indyNamespace: 'pool1' }, -} as IndyPool) - -const agentConfig = getAgentConfig('IndySdkSovDidResolver') - -const wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) -mockProperty(wallet, 'handle', 10) - -const agentContext = getAgentContext({ - agentConfig, - registerInstances: [[IndyPoolService, indyPoolServiceMock]], -}) - -const indySdkSovDidResolver = new IndySdkSovDidResolver() - -describe('DidResolver', () => { - describe('IndySdkSovDidResolver', () => { - it('should correctly resolve a did:sov document', async () => { - const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' - - const nymResponse: GetNymResponse = { - did: 'R1xKJw17sUoXhejEpugMYJ', - verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - role: 'ENDORSER', - } - - const endpoints: IndyEndpointAttrib = { - endpoint: 'https://ssi.com', - profile: 'https://profile.com', - hub: 'https://hub.com', - } - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) - - const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) - }) - - it('should resolve a did:sov document with routingKeys and types entries in the attrib', async () => { - const did = 'did:sov:WJz9mHyW9BZksioQnRsrAo' - - const nymResponse: GetNymResponse = { - did: 'WJz9mHyW9BZksioQnRsrAo', - verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', - role: 'ENDORSER', - } - - const endpoints: IndyEndpointAttrib = { - endpoint: 'https://agent.com', - types: ['endpoint', 'did-communication', 'DIDComm'], - routingKeys: ['routingKey1', 'routingKey2'], - } - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) - - const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) - }) - - it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { - const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockRejectedValue(new Error('Error retrieving did')) - - const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) - - expect(result).toMatchObject({ - didDocument: null, - didDocumentMetadata: {}, - didResolutionMetadata: { - error: 'notFound', - message: `resolver_error: Unable to resolve did 'did:sov:R1xKJw17sUoXhejEpugMYJ': Error: Error retrieving did`, - }, - }) - }) - }) -}) diff --git a/packages/core/src/modules/dids/methods/sov/index.ts b/packages/core/src/modules/dids/methods/sov/index.ts deleted file mode 100644 index 13a8e8aa2f..0000000000 --- a/packages/core/src/modules/dids/methods/sov/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './IndySdkSovDidRegistrar' -export * from './IndySdkSovDidResolver' diff --git a/packages/core/src/modules/dids/methods/sov/util.ts b/packages/core/src/modules/dids/methods/sov/util.ts deleted file mode 100644 index 638779dd21..0000000000 --- a/packages/core/src/modules/dids/methods/sov/util.ts +++ /dev/null @@ -1,123 +0,0 @@ -import type { IndyEndpointAttrib } from '../../../ledger' - -import { TypedArrayEncoder } from '../../../../utils' -import { getFullVerkey } from '../../../../utils/did' -import { SECURITY_X25519_CONTEXT_URL } from '../../../vc/constants' -import { ED25519_SUITE_CONTEXT_URL_2018 } from '../../../vc/signature-suites/ed25519/constants' -import { DidDocumentService, DidDocumentBuilder, DidCommV1Service, DidCommV2Service } from '../../domain' -import { convertPublicKeyToX25519 } from '../../domain/key-type/ed25519' - -export function sovDidDocumentFromDid(fullDid: string, verkey: string) { - const verificationMethodId = `${fullDid}#key-1` - const keyAgreementId = `${fullDid}#key-agreement-1` - - const publicKeyBase58 = getFullVerkey(fullDid, verkey) - const publicKeyX25519 = TypedArrayEncoder.toBase58( - convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(publicKeyBase58)) - ) - - const builder = new DidDocumentBuilder(fullDid) - .addContext(ED25519_SUITE_CONTEXT_URL_2018) - .addContext(SECURITY_X25519_CONTEXT_URL) - .addVerificationMethod({ - controller: fullDid, - id: verificationMethodId, - publicKeyBase58: publicKeyBase58, - type: 'Ed25519VerificationKey2018', - }) - .addVerificationMethod({ - controller: fullDid, - id: keyAgreementId, - publicKeyBase58: publicKeyX25519, - type: 'X25519KeyAgreementKey2019', - }) - .addAuthentication(verificationMethodId) - .addAssertionMethod(verificationMethodId) - .addKeyAgreement(keyAgreementId) - - return builder -} - -// Process Indy Attrib Endpoint Types according to: https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html > Read (Resolve) > DID Service Endpoint -function processEndpointTypes(types?: string[]) { - const expectedTypes = ['endpoint', 'did-communication', 'DIDComm'] - const defaultTypes = ['endpoint', 'did-communication'] - - // Return default types if types "is NOT present [or] empty" - if (!types || types.length <= 0) { - return defaultTypes - } - - // Return default types if types "contain any other values" - for (const type of types) { - if (!expectedTypes.includes(type)) { - return defaultTypes - } - } - - // Return provided types - return types -} - -export function addServicesFromEndpointsAttrib( - builder: DidDocumentBuilder, - did: string, - endpoints: IndyEndpointAttrib, - keyAgreementId: string -) { - const { endpoint, routingKeys, types, ...otherEndpoints } = endpoints - - if (endpoint) { - const processedTypes = processEndpointTypes(types) - - // If 'endpoint' included in types, add id to the services array - if (processedTypes.includes('endpoint')) { - builder.addService( - new DidDocumentService({ - id: `${did}#endpoint`, - serviceEndpoint: endpoint, - type: 'endpoint', - }) - ) - } - - // If 'did-communication' included in types, add DIDComm v1 entry - if (processedTypes.includes('did-communication')) { - builder.addService( - new DidCommV1Service({ - id: `${did}#did-communication`, - serviceEndpoint: endpoint, - priority: 0, - routingKeys: routingKeys ?? [], - recipientKeys: [keyAgreementId], - accept: ['didcomm/aip2;env=rfc19'], - }) - ) - - // If 'DIDComm' included in types, add DIDComm v2 entry - if (processedTypes.includes('DIDComm')) { - builder - .addService( - new DidCommV2Service({ - id: `${did}#didcomm-1`, - serviceEndpoint: endpoint, - routingKeys: routingKeys ?? [], - accept: ['didcomm/v2'], - }) - ) - .addContext('https://didcomm.org/messaging/contexts/v2') - } - } - } - - // Add other endpoint types - for (const [type, endpoint] of Object.entries(otherEndpoints)) { - builder.addService( - new DidDocumentService({ - id: `${did}#${type}`, - serviceEndpoint: endpoint as string, - type, - }) - ) - } -} diff --git a/packages/core/src/modules/indy/IndyModule.ts b/packages/core/src/modules/indy/IndyModule.ts deleted file mode 100644 index 563a853874..0000000000 --- a/packages/core/src/modules/indy/IndyModule.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { DependencyManager, Module } from '../../plugins' - -import { IndyRevocationService, IndyUtilitiesService } from './services' -import { IndyHolderService } from './services/IndyHolderService' -import { IndyIssuerService } from './services/IndyIssuerService' -import { IndyVerifierService } from './services/IndyVerifierService' - -export class IndyModule implements Module { - public register(dependencyManager: DependencyManager) { - dependencyManager.registerSingleton(IndyIssuerService) - dependencyManager.registerSingleton(IndyHolderService) - dependencyManager.registerSingleton(IndyVerifierService) - dependencyManager.registerSingleton(IndyRevocationService) - dependencyManager.registerSingleton(IndyUtilitiesService) - } -} diff --git a/packages/core/src/modules/indy/__tests__/IndyModule.test.ts b/packages/core/src/modules/indy/__tests__/IndyModule.test.ts deleted file mode 100644 index edad08f2d6..0000000000 --- a/packages/core/src/modules/indy/__tests__/IndyModule.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { DependencyManager } from '../../../plugins/DependencyManager' -import { IndyModule } from '../IndyModule' -import { - IndyHolderService, - IndyIssuerService, - IndyVerifierService, - IndyRevocationService, - IndyUtilitiesService, -} from '../services' - -jest.mock('../../../plugins/DependencyManager') -const DependencyManagerMock = DependencyManager as jest.Mock - -const dependencyManager = new DependencyManagerMock() - -describe('IndyModule', () => { - test('registers dependencies on the dependency manager', () => { - new IndyModule().register(dependencyManager) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(5) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyHolderService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyIssuerService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyRevocationService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyVerifierService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyUtilitiesService) - }) -}) diff --git a/packages/core/src/modules/indy/index.ts b/packages/core/src/modules/indy/index.ts deleted file mode 100644 index 5b289c3de8..0000000000 --- a/packages/core/src/modules/indy/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './services' -export * from './IndyModule' diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts deleted file mode 100644 index 699abb6148..0000000000 --- a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { CredDef } from 'indy-sdk' - -import { BaseRecord } from '../../../storage/BaseRecord' -import { uuid } from '../../../utils/uuid' - -export interface AnonCredsCredentialDefinitionRecordProps { - credentialDefinition: CredDef -} - -export class AnonCredsCredentialDefinitionRecord extends BaseRecord { - public static readonly type = 'AnonCredsCredentialDefinitionRecord' - public readonly type = AnonCredsCredentialDefinitionRecord.type - - public readonly credentialDefinition!: CredDef - - public constructor(props: AnonCredsCredentialDefinitionRecordProps) { - super() - - if (props) { - this.id = uuid() - this.credentialDefinition = props.credentialDefinition - } - } - - public getTags() { - return { - ...this._tags, - credentialDefinitionId: this.credentialDefinition.id, - } - } -} diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts deleted file mode 100644 index 706c7e2cac..0000000000 --- a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRepository.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' - -import { EventEmitter } from '../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../constants' -import { injectable, inject } from '../../../plugins' -import { Repository } from '../../../storage/Repository' -import { StorageService } from '../../../storage/StorageService' - -import { AnonCredsCredentialDefinitionRecord } from './AnonCredsCredentialDefinitionRecord' - -@injectable() -export class AnonCredsCredentialDefinitionRepository extends Repository { - public constructor( - @inject(InjectionSymbols.StorageService) storageService: StorageService, - eventEmitter: EventEmitter - ) { - super(AnonCredsCredentialDefinitionRecord, storageService, eventEmitter) - } - - public async getByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { - return this.getSingleByQuery(agentContext, { credentialDefinitionId }) - } - - public async findByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) { - return this.findSingleByQuery(agentContext, { credentialDefinitionId }) - } -} diff --git a/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts deleted file mode 100644 index 70eb12df38..0000000000 --- a/packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { Schema } from 'indy-sdk' - -import { BaseRecord } from '../../../storage/BaseRecord' -import { didFromSchemaId } from '../../../utils/did' -import { uuid } from '../../../utils/uuid' - -export interface AnonCredsSchemaRecordProps { - schema: Schema - id?: string -} - -export type DefaultAnonCredsSchemaTags = { - schemaId: string - schemaIssuerDid: string - schemaName: string - schemaVersion: string -} - -export class AnonCredsSchemaRecord extends BaseRecord { - public static readonly type = 'AnonCredsSchemaRecord' - public readonly type = AnonCredsSchemaRecord.type - - public readonly schema!: Schema - - public constructor(props: AnonCredsSchemaRecordProps) { - super() - - if (props) { - this.id = props.id ?? uuid() - this.schema = props.schema - } - } - - public getTags() { - return { - ...this._tags, - schemaId: this.schema.id, - schemaIssuerDid: didFromSchemaId(this.schema.id), - schemaName: this.schema.name, - schemaVersion: this.schema.version, - } - } -} diff --git a/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts b/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts deleted file mode 100644 index 311931f1f6..0000000000 --- a/packages/core/src/modules/indy/repository/AnonCredsSchemaRepository.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' - -import { EventEmitter } from '../../../agent/EventEmitter' -import { InjectionSymbols } from '../../../constants' -import { injectable, inject } from '../../../plugins' -import { Repository } from '../../../storage/Repository' -import { StorageService } from '../../../storage/StorageService' - -import { AnonCredsSchemaRecord } from './AnonCredsSchemaRecord' - -@injectable() -export class AnonCredsSchemaRepository extends Repository { - public constructor( - @inject(InjectionSymbols.StorageService) storageService: StorageService, - eventEmitter: EventEmitter - ) { - super(AnonCredsSchemaRecord, storageService, eventEmitter) - } - - public async getBySchemaId(agentContext: AgentContext, schemaId: string) { - return this.getSingleByQuery(agentContext, { schemaId: schemaId }) - } - - public async findBySchemaId(agentContext: AgentContext, schemaId: string) { - return await this.findSingleByQuery(agentContext, { schemaId: schemaId }) - } -} diff --git a/packages/core/src/modules/indy/services/IndyHolderService.ts b/packages/core/src/modules/indy/services/IndyHolderService.ts deleted file mode 100644 index a53f2b7049..0000000000 --- a/packages/core/src/modules/indy/services/IndyHolderService.ts +++ /dev/null @@ -1,294 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { RequestedCredentials } from '../../proofs/formats/indy/models/RequestedCredentials' -import type * as Indy from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { isIndyError } from '../../../utils/indyError' -import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' - -import { IndyRevocationService } from './IndyRevocationService' - -@injectable() -export class IndyHolderService { - private indy: typeof Indy - private logger: Logger - private indyRevocationService: IndyRevocationService - - public constructor( - indyRevocationService: IndyRevocationService, - @inject(InjectionSymbols.Logger) logger: Logger, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies - ) { - this.indy = agentDependencies.indy - this.indyRevocationService = indyRevocationService - this.logger = logger - } - - /** - * Creates an Indy Proof in response to a proof request. Will create revocation state if the proof request requests proof of non-revocation - * - * @param proofRequest a Indy proof request - * @param requestedCredentials the requested credentials to use for the proof creation - * @param schemas schemas to use in proof creation - * @param credentialDefinitions credential definitions to use in proof creation - * @throws {Error} if there is an error during proof generation or revocation state generation - * @returns a promise of Indy Proof - * - * @todo support attribute non_revoked fields - */ - public async createProof( - agentContext: AgentContext, - { proofRequest, requestedCredentials, schemas, credentialDefinitions }: CreateProofOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - this.logger.debug('Creating Indy Proof') - const revocationStates: Indy.RevStates = await this.indyRevocationService.createRevocationState( - agentContext, - proofRequest, - requestedCredentials - ) - - const indyProof: Indy.IndyProof = await this.indy.proverCreateProof( - agentContext.wallet.handle, - proofRequest, - requestedCredentials.toJSON(), - agentContext.wallet.masterSecretId, - schemas, - credentialDefinitions, - revocationStates - ) - - this.logger.trace('Created Indy Proof', { - indyProof, - }) - - return indyProof - } catch (error) { - this.logger.error(`Error creating Indy Proof`, { - error, - proofRequest, - requestedCredentials, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Store a credential in the wallet. - * - * @returns The credential id - */ - public async storeCredential( - agentContext: AgentContext, - { - credentialRequestMetadata, - credential, - credentialDefinition, - credentialId, - revocationRegistryDefinition, - }: StoreCredentialOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.proverStoreCredential( - agentContext.wallet.handle, - credentialId ?? null, - credentialRequestMetadata, - credential, - credentialDefinition, - revocationRegistryDefinition ?? null - ) - } catch (error) { - this.logger.error(`Error storing Indy Credential '${credentialId}'`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Get a credential stored in the wallet by id. - * - * @param credentialId the id (referent) of the credential - * @throws {Error} if the credential is not found - * @returns the credential - * - * @todo handle record not found - */ - public async getCredential( - agentContext: AgentContext, - credentialId: Indy.CredentialId - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.proverGetCredential(agentContext.wallet.handle, credentialId) - } catch (error) { - this.logger.error(`Error getting Indy Credential '${credentialId}'`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Create a credential request for the given credential offer. - * - * @returns The credential request and the credential request metadata - */ - public async createCredentialRequest( - agentContext: AgentContext, - { holderDid, credentialOffer, credentialDefinition }: CreateCredentialRequestOptions - ): Promise<[Indy.CredReq, Indy.CredReqMetadata]> { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.proverCreateCredentialReq( - agentContext.wallet.handle, - holderDid, - credentialOffer, - credentialDefinition, - agentContext.wallet.masterSecretId - ) - } catch (error) { - this.logger.error(`Error creating Indy Credential Request`, { - error, - credentialOffer, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Retrieve the credentials that are available for an attribute referent in the proof request. - * - * @param proofRequest The proof request to retrieve the credentials for - * @param attributeReferent An attribute referent from the proof request to retrieve the credentials for - * @param start Starting index - * @param limit Maximum number of records to return - * - * @returns List of credentials that are available for building a proof for the given proof request - * - */ - public async getCredentialsForProofRequest( - agentContext: AgentContext, - { proofRequest, attributeReferent, start = 0, limit = 256, extraQuery }: GetCredentialForProofRequestOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - // Open indy credential search - const searchHandle = await this.indy.proverSearchCredentialsForProofReq( - agentContext.wallet.handle, - proofRequest, - extraQuery ?? null - ) - - try { - // Make sure database cursors start at 'start' (bit ugly, but no way around in indy) - if (start > 0) { - await this.fetchCredentialsForReferent(searchHandle, attributeReferent, start) - } - - // Fetch the credentials - const credentials = await this.fetchCredentialsForReferent(searchHandle, attributeReferent, limit) - - // TODO: sort the credentials (irrevocable first) - return credentials - } finally { - // Always close search - await this.indy.proverCloseCredentialsSearchForProofReq(searchHandle) - } - } catch (error) { - if (isIndyError(error)) { - throw new IndySdkError(error) - } - - throw error - } - } - - /** - * Delete a credential stored in the wallet by id. - * - * @param credentialId the id (referent) of the credential - * - */ - public async deleteCredential(agentContext: AgentContext, credentialId: Indy.CredentialId): Promise { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.proverDeleteCredential(agentContext.wallet.handle, credentialId) - } catch (error) { - this.logger.error(`Error deleting Indy Credential from Wallet`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async fetchCredentialsForReferent(searchHandle: number, referent: string, limit?: number) { - try { - let credentials: Indy.IndyCredential[] = [] - - // Allow max of 256 per fetch operation - const chunk = limit ? Math.min(256, limit) : 256 - - // Loop while limit not reached (or no limit specified) - while (!limit || credentials.length < limit) { - // Retrieve credentials - const credentialsJson = await this.indy.proverFetchCredentialsForProofReq(searchHandle, referent, chunk) - credentials = [...credentials, ...credentialsJson] - - // If the number of credentials returned is less than chunk - // It means we reached the end of the iterator (no more credentials) - if (credentialsJson.length < chunk) { - return credentials - } - } - - return credentials - } catch (error) { - this.logger.error(`Error Fetching Indy Credentials For Referent`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} - -export interface GetCredentialForProofRequestOptions { - proofRequest: Indy.IndyProofRequest - attributeReferent: string - start?: number - limit?: number - extraQuery?: Indy.ReferentWalletQuery -} - -export interface CreateCredentialRequestOptions { - holderDid: string - credentialOffer: Indy.CredOffer - credentialDefinition: Indy.CredDef -} - -export interface StoreCredentialOptions { - credentialRequestMetadata: Indy.CredReqMetadata - credential: Indy.Cred - credentialDefinition: Indy.CredDef - credentialId?: Indy.CredentialId - revocationRegistryDefinition?: Indy.RevocRegDef -} - -export interface CreateProofOptions { - proofRequest: Indy.IndyProofRequest - requestedCredentials: RequestedCredentials - schemas: Indy.Schemas - credentialDefinitions: Indy.CredentialDefs -} diff --git a/packages/core/src/modules/indy/services/IndyIssuerService.ts b/packages/core/src/modules/indy/services/IndyIssuerService.ts deleted file mode 100644 index 58e9917cf0..0000000000 --- a/packages/core/src/modules/indy/services/IndyIssuerService.ts +++ /dev/null @@ -1,166 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { - Cred, - CredDef, - CredDefId, - CredOffer, - CredReq, - CredRevocId, - CredValues, - default as Indy, - Schema, -} from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { IndySdkError } from '../../../error/IndySdkError' -import { injectable, inject } from '../../../plugins' -import { isIndyError } from '../../../utils/indyError' -import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' - -import { IndyUtilitiesService } from './IndyUtilitiesService' - -@injectable() -export class IndyIssuerService { - private indy: typeof Indy - private indyUtilitiesService: IndyUtilitiesService - - public constructor( - indyUtilitiesService: IndyUtilitiesService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies - ) { - this.indy = agentDependencies.indy - this.indyUtilitiesService = indyUtilitiesService - } - - /** - * Create a new credential schema. - * - * @returns the schema. - */ - public async createSchema( - agentContext: AgentContext, - { originDid, name, version, attributes }: CreateSchemaOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - const [, schema] = await this.indy.issuerCreateSchema(originDid, name, version, attributes) - - return schema - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Create a new credential definition and store it in the wallet. - * - * @returns the credential definition. - */ - public async createCredentialDefinition( - agentContext: AgentContext, - { - issuerDid, - schema, - tag = 'default', - signatureType = 'CL', - supportRevocation = false, - }: CreateCredentialDefinitionOptions - ): Promise { - assertIndyWallet(agentContext.wallet) - try { - const [, credentialDefinition] = await this.indy.issuerCreateAndStoreCredentialDef( - agentContext.wallet.handle, - issuerDid, - schema, - tag, - signatureType, - { - support_revocation: supportRevocation, - } - ) - - return credentialDefinition - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Create a credential offer for the given credential definition id. - * - * @param credentialDefinitionId The credential definition to create an offer for - * @returns The created credential offer - */ - public async createCredentialOffer(agentContext: AgentContext, credentialDefinitionId: CredDefId) { - assertIndyWallet(agentContext.wallet) - try { - return await this.indy.issuerCreateCredentialOffer(agentContext.wallet.handle, credentialDefinitionId) - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * Create a credential. - * - * @returns Credential and revocation id - */ - public async createCredential( - agentContext: AgentContext, - { - credentialOffer, - credentialRequest, - credentialValues, - revocationRegistryId, - tailsFilePath, - }: CreateCredentialOptions - ): Promise<[Cred, CredRevocId]> { - assertIndyWallet(agentContext.wallet) - try { - // Indy SDK requires tailsReaderHandle. Use null if no tailsFilePath is present - const tailsReaderHandle = tailsFilePath ? await this.indyUtilitiesService.createTailsReader(tailsFilePath) : 0 - - if (revocationRegistryId || tailsFilePath) { - throw new AriesFrameworkError('Revocation not supported yet') - } - - const [credential, credentialRevocationId] = await this.indy.issuerCreateCredential( - agentContext.wallet.handle, - credentialOffer, - credentialRequest, - credentialValues, - revocationRegistryId ?? null, - tailsReaderHandle - ) - - return [credential, credentialRevocationId] - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} - -export interface CreateCredentialDefinitionOptions { - issuerDid: string - schema: Schema - tag?: string - signatureType?: 'CL' - supportRevocation?: boolean -} - -export interface CreateCredentialOptions { - credentialOffer: CredOffer - credentialRequest: CredReq - credentialValues: CredValues - revocationRegistryId?: string - tailsFilePath?: string -} - -export interface CreateSchemaOptions { - originDid: string - name: string - version: string - attributes: string[] -} diff --git a/packages/core/src/modules/indy/services/IndyRevocationService.ts b/packages/core/src/modules/indy/services/IndyRevocationService.ts deleted file mode 100644 index c1caf5b297..0000000000 --- a/packages/core/src/modules/indy/services/IndyRevocationService.ts +++ /dev/null @@ -1,198 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { IndyRevocationInterval } from '../../credentials' -import type { RequestedCredentials } from '../../proofs/formats/indy/models/RequestedCredentials' -import type { default as Indy, RevStates } from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { isIndyError } from '../../../utils/indyError' -import { IndyLedgerService } from '../../ledger' - -import { IndyUtilitiesService } from './IndyUtilitiesService' - -enum RequestReferentType { - Attribute = 'attribute', - Predicate = 'predicate', - SelfAttestedAttribute = 'self-attested-attribute', -} -@injectable() -export class IndyRevocationService { - private indy: typeof Indy - private indyUtilitiesService: IndyUtilitiesService - private ledgerService: IndyLedgerService - private logger: Logger - - public constructor( - indyUtilitiesService: IndyUtilitiesService, - ledgerService: IndyLedgerService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger - ) { - this.indy = agentDependencies.indy - this.indyUtilitiesService = indyUtilitiesService - this.logger = logger - this.ledgerService = ledgerService - } - - public async createRevocationState( - agentContext: AgentContext, - proofRequest: Indy.IndyProofRequest, - requestedCredentials: RequestedCredentials - ): Promise { - try { - this.logger.debug(`Creating Revocation State(s) for proof request`, { - proofRequest, - requestedCredentials, - }) - const revocationStates: RevStates = {} - const referentCredentials = [] - - //Retrieve information for referents and push to single array - for (const [referent, requestedCredential] of Object.entries(requestedCredentials.requestedAttributes)) { - referentCredentials.push({ - referent, - credentialInfo: requestedCredential.credentialInfo, - type: RequestReferentType.Attribute, - }) - } - for (const [referent, requestedCredential] of Object.entries(requestedCredentials.requestedPredicates)) { - referentCredentials.push({ - referent, - credentialInfo: requestedCredential.credentialInfo, - type: RequestReferentType.Predicate, - }) - } - - for (const { referent, credentialInfo, type } of referentCredentials) { - if (!credentialInfo) { - throw new AriesFrameworkError( - `Credential for referent '${referent} does not have credential info for revocation state creation` - ) - } - - // Prefer referent-specific revocation interval over global revocation interval - const referentRevocationInterval = - type === RequestReferentType.Predicate - ? proofRequest.requested_predicates[referent].non_revoked - : proofRequest.requested_attributes[referent].non_revoked - const requestRevocationInterval = referentRevocationInterval ?? proofRequest.non_revoked - const credentialRevocationId = credentialInfo.credentialRevocationId - const revocationRegistryId = credentialInfo.revocationRegistryId - - // If revocation interval is present and the credential is revocable then create revocation state - if (requestRevocationInterval && credentialRevocationId && revocationRegistryId) { - this.logger.trace( - `Presentation is requesting proof of non revocation for ${type} referent '${referent}', creating revocation state for credential`, - { - requestRevocationInterval, - credentialRevocationId, - revocationRegistryId, - } - ) - - this.assertRevocationInterval(requestRevocationInterval) - - const { revocationRegistryDefinition } = await this.ledgerService.getRevocationRegistryDefinition( - agentContext, - revocationRegistryId - ) - - const { revocationRegistryDelta, deltaTimestamp } = await this.ledgerService.getRevocationRegistryDelta( - agentContext, - revocationRegistryId, - requestRevocationInterval?.to, - 0 - ) - - const { tailsLocation, tailsHash } = revocationRegistryDefinition.value - const tails = await this.indyUtilitiesService.downloadTails(tailsHash, tailsLocation) - - const revocationState = await this.indy.createRevocationState( - tails, - revocationRegistryDefinition, - revocationRegistryDelta, - deltaTimestamp, - credentialRevocationId - ) - const timestamp = revocationState.timestamp - - if (!revocationStates[revocationRegistryId]) { - revocationStates[revocationRegistryId] = {} - } - revocationStates[revocationRegistryId][timestamp] = revocationState - } - } - - this.logger.debug(`Created Revocation States for Proof Request`, { - revocationStates, - }) - - return revocationStates - } catch (error) { - this.logger.error(`Error creating Indy Revocation State for Proof Request`, { - error, - proofRequest, - requestedCredentials, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - // Get revocation status for credential (given a from-to) - // Note from-to interval details: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals - public async getRevocationStatus( - agentContext: AgentContext, - credentialRevocationId: string, - revocationRegistryDefinitionId: string, - requestRevocationInterval: IndyRevocationInterval - ): Promise<{ revoked: boolean; deltaTimestamp: number }> { - this.logger.trace( - `Fetching Credential Revocation Status for Credential Revocation Id '${credentialRevocationId}' with revocation interval with to '${requestRevocationInterval.to}' & from '${requestRevocationInterval.from}'` - ) - - this.assertRevocationInterval(requestRevocationInterval) - - const { revocationRegistryDelta, deltaTimestamp } = await this.ledgerService.getRevocationRegistryDelta( - agentContext, - revocationRegistryDefinitionId, - requestRevocationInterval.to, - 0 - ) - - const revoked: boolean = revocationRegistryDelta.value.revoked?.includes(parseInt(credentialRevocationId)) || false - this.logger.trace( - `Credential with Credential Revocation Id '${credentialRevocationId}' is ${ - revoked ? '' : 'not ' - }revoked with revocation interval with to '${requestRevocationInterval.to}' & from '${ - requestRevocationInterval.from - }'` - ) - - return { - revoked, - deltaTimestamp, - } - } - - // TODO: Add Test - // Check revocation interval in accordance with https://github.com/hyperledger/aries-rfcs/blob/main/concepts/0441-present-proof-best-practices/README.md#semantics-of-non-revocation-interval-endpoints - private assertRevocationInterval(requestRevocationInterval: IndyRevocationInterval) { - if (!requestRevocationInterval.to) { - throw new AriesFrameworkError(`Presentation requests proof of non-revocation with no 'to' value specified`) - } - - if ( - (requestRevocationInterval.from || requestRevocationInterval.from === 0) && - requestRevocationInterval.to !== requestRevocationInterval.from - ) { - throw new AriesFrameworkError( - `Presentation requests proof of non-revocation with an interval from: '${requestRevocationInterval.from}' that does not match the interval to: '${requestRevocationInterval.to}', as specified in Aries RFC 0441` - ) - } - } -} diff --git a/packages/core/src/modules/indy/services/IndyUtilitiesService.ts b/packages/core/src/modules/indy/services/IndyUtilitiesService.ts deleted file mode 100644 index eef01ccfd2..0000000000 --- a/packages/core/src/modules/indy/services/IndyUtilitiesService.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { BlobReaderHandle, default as Indy } from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { AriesFrameworkError } from '../../../error' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { FileSystem } from '../../../storage/FileSystem' -import { isIndyError } from '../../../utils/indyError' -import { getDirFromFilePath } from '../../../utils/path' - -@injectable() -export class IndyUtilitiesService { - private indy: typeof Indy - private logger: Logger - private fileSystem: FileSystem - - public constructor( - @inject(InjectionSymbols.Logger) logger: Logger, - @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies - ) { - this.indy = agentDependencies.indy - this.logger = logger - this.fileSystem = fileSystem - } - - /** - * Get a handler for the blob storage tails file reader. - * - * @param tailsFilePath The path of the tails file - * @returns The blob storage reader handle - */ - public async createTailsReader(tailsFilePath: string): Promise { - try { - this.logger.debug(`Opening tails reader at path ${tailsFilePath}`) - const tailsFileExists = await this.fileSystem.exists(tailsFilePath) - - // Extract directory from path (should also work with windows paths) - const dirname = getDirFromFilePath(tailsFilePath) - - if (!tailsFileExists) { - throw new AriesFrameworkError(`Tails file does not exist at path ${tailsFilePath}`) - } - - const tailsReaderConfig = { - base_dir: dirname, - } - - const tailsReader = await this.indy.openBlobStorageReader('default', tailsReaderConfig) - this.logger.debug(`Opened tails reader at path ${tailsFilePath}`) - return tailsReader - } catch (error) { - if (isIndyError(error)) { - throw new IndySdkError(error) - } - - throw error - } - } - - public async downloadTails(hash: string, tailsLocation: string): Promise { - try { - this.logger.debug(`Checking to see if tails file for URL ${tailsLocation} has been stored in the FileSystem`) - const filePath = `${this.fileSystem.basePath}/afj/tails/${hash}` - - const tailsExists = await this.fileSystem.exists(filePath) - this.logger.debug(`Tails file for ${tailsLocation} ${tailsExists ? 'is stored' : 'is not stored'} at ${filePath}`) - if (!tailsExists) { - this.logger.debug(`Retrieving tails file from URL ${tailsLocation}`) - - await this.fileSystem.downloadToFile(tailsLocation, filePath) - this.logger.debug(`Saved tails file to FileSystem at path ${filePath}`) - - //TODO: Validate Tails File Hash - } - - this.logger.debug(`Tails file for URL ${tailsLocation} is stored in the FileSystem, opening tails reader`) - return this.createTailsReader(filePath) - } catch (error) { - this.logger.error(`Error while retrieving tails file from URL ${tailsLocation}`, { - error, - }) - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} diff --git a/packages/core/src/modules/indy/services/IndyVerifierService.ts b/packages/core/src/modules/indy/services/IndyVerifierService.ts deleted file mode 100644 index c6ad15bb77..0000000000 --- a/packages/core/src/modules/indy/services/IndyVerifierService.ts +++ /dev/null @@ -1,85 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type * as Indy from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { IndySdkError } from '../../../error' -import { injectable, inject } from '../../../plugins' -import { isIndyError } from '../../../utils/indyError' -import { IndyLedgerService } from '../../ledger/services/IndyLedgerService' - -@injectable() -export class IndyVerifierService { - private indy: typeof Indy - private ledgerService: IndyLedgerService - - public constructor( - ledgerService: IndyLedgerService, - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies - ) { - this.indy = agentDependencies.indy - this.ledgerService = ledgerService - } - - public async verifyProof( - agentContext: AgentContext, - { proofRequest, proof, schemas, credentialDefinitions }: VerifyProofOptions - ): Promise { - try { - const { revocationRegistryDefinitions, revocationRegistries } = await this.getRevocationRegistries( - agentContext, - proof - ) - - return await this.indy.verifierVerifyProof( - proofRequest, - proof, - schemas, - credentialDefinitions, - revocationRegistryDefinitions, - revocationRegistries - ) - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async getRevocationRegistries(agentContext: AgentContext, proof: Indy.IndyProof) { - const revocationRegistryDefinitions: Indy.RevocRegDefs = {} - const revocationRegistries: Indy.RevRegs = Object.create(null) - for (const identifier of proof.identifiers) { - const revocationRegistryId = identifier.rev_reg_id - const timestamp = identifier.timestamp - - //Fetch Revocation Registry Definition if not already fetched - if (revocationRegistryId && !revocationRegistryDefinitions[revocationRegistryId]) { - const { revocationRegistryDefinition } = await this.ledgerService.getRevocationRegistryDefinition( - agentContext, - revocationRegistryId - ) - revocationRegistryDefinitions[revocationRegistryId] = revocationRegistryDefinition - } - - //Fetch Revocation Registry by Timestamp if not already fetched - if (revocationRegistryId && timestamp && !revocationRegistries[revocationRegistryId]?.[timestamp]) { - if (!revocationRegistries[revocationRegistryId]) { - revocationRegistries[revocationRegistryId] = Object.create(null) - } - const { revocationRegistry } = await this.ledgerService.getRevocationRegistry( - agentContext, - revocationRegistryId, - timestamp - ) - revocationRegistries[revocationRegistryId][timestamp] = revocationRegistry - } - } - return { revocationRegistryDefinitions, revocationRegistries } - } -} - -export interface VerifyProofOptions { - proofRequest: Indy.IndyProofRequest - proof: Indy.IndyProof - schemas: Indy.Schemas - credentialDefinitions: Indy.CredentialDefs -} diff --git a/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts b/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts deleted file mode 100644 index 35afdc14ab..0000000000 --- a/packages/core/src/modules/indy/services/__mocks__/IndyHolderService.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { CreateCredentialRequestOptions, StoreCredentialOptions } from '../IndyHolderService' - -export const IndyHolderService = jest.fn(() => ({ - storeCredential: jest.fn((_, { credentialId }: StoreCredentialOptions) => - Promise.resolve(credentialId ?? 'some-random-uuid') - ), - deleteCredential: jest.fn(() => Promise.resolve()), - createCredentialRequest: jest.fn((_, { holderDid, credentialDefinition }: CreateCredentialRequestOptions) => - Promise.resolve([ - { - prover_did: holderDid, - cred_def_id: credentialDefinition.id, - blinded_ms: {}, - blinded_ms_correctness_proof: {}, - nonce: 'nonce', - }, - { cred_req: 'meta-data' }, - ]) - ), -})) diff --git a/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts b/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts deleted file mode 100644 index 823e961a15..0000000000 --- a/packages/core/src/modules/indy/services/__mocks__/IndyIssuerService.ts +++ /dev/null @@ -1,25 +0,0 @@ -export const IndyIssuerService = jest.fn(() => ({ - createCredential: jest.fn(() => - Promise.resolve([ - { - schema_id: 'schema_id', - cred_def_id: 'cred_def_id', - rev_reg_def_id: 'rev_reg_def_id', - values: {}, - signature: 'signature', - signature_correctness_proof: 'signature_correctness_proof', - }, - '1', - ]) - ), - - createCredentialOffer: jest.fn((_, credentialDefinitionId: string) => - Promise.resolve({ - schema_id: 'aaa', - cred_def_id: credentialDefinitionId, - // Fields below can depend on Cred Def type - nonce: 'nonce', - key_correctness_proof: {}, - }) - ), -})) diff --git a/packages/core/src/modules/indy/services/__mocks__/IndyVerifierService.ts b/packages/core/src/modules/indy/services/__mocks__/IndyVerifierService.ts deleted file mode 100644 index 483a384f67..0000000000 --- a/packages/core/src/modules/indy/services/__mocks__/IndyVerifierService.ts +++ /dev/null @@ -1 +0,0 @@ -export const IndyVerifierService = jest.fn(() => ({})) diff --git a/packages/core/src/modules/indy/services/index.ts b/packages/core/src/modules/indy/services/index.ts deleted file mode 100644 index fa01eaf419..0000000000 --- a/packages/core/src/modules/indy/services/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './IndyHolderService' -export * from './IndyIssuerService' -export * from './IndyVerifierService' -export * from './IndyUtilitiesService' -export * from './IndyRevocationService' diff --git a/packages/core/src/modules/ledger/IndyPool.ts b/packages/core/src/modules/ledger/IndyPool.ts deleted file mode 100644 index c3d2249799..0000000000 --- a/packages/core/src/modules/ledger/IndyPool.ts +++ /dev/null @@ -1,209 +0,0 @@ -import type { AgentDependencies } from '../../agent/AgentDependencies' -import type { Logger } from '../../logger' -import type { FileSystem } from '../../storage/FileSystem' -import type { DidIndyNamespace } from '../../utils/indyIdentifiers' -import type * as Indy from 'indy-sdk' -import type { Subject } from 'rxjs' - -import { AriesFrameworkError, IndySdkError } from '../../error' -import { isIndyError } from '../../utils/indyError' - -import { LedgerError } from './error/LedgerError' -import { isLedgerRejectResponse, isLedgerReqnackResponse } from './ledgerUtil' - -export interface TransactionAuthorAgreement { - version: `${number}.${number}` | `${number}` - acceptanceMechanism: string -} - -export interface IndyPoolConfig { - genesisPath?: string - genesisTransactions?: string - id: string - isProduction: boolean - indyNamespace: DidIndyNamespace - transactionAuthorAgreement?: TransactionAuthorAgreement -} - -export class IndyPool { - private indy: typeof Indy - private logger: Logger - private fileSystem: FileSystem - private poolConfig: IndyPoolConfig - private _poolHandle?: number - private poolConnected?: Promise - public authorAgreement?: AuthorAgreement | null - - public constructor( - poolConfig: IndyPoolConfig, - agentDependencies: AgentDependencies, - logger: Logger, - stop$: Subject, - fileSystem: FileSystem - ) { - this.indy = agentDependencies.indy - this.fileSystem = fileSystem - this.poolConfig = poolConfig - this.logger = logger - - // Listen to stop$ (shutdown) and close pool - stop$.subscribe(async () => { - if (this._poolHandle) { - await this.close() - } - }) - } - - public get didIndyNamespace(): string { - return this.didIndyNamespace - } - - public get id() { - return this.poolConfig.id - } - - public get config() { - return this.poolConfig - } - - public async close() { - const poolHandle = this._poolHandle - - if (!poolHandle) { - return - } - - this._poolHandle = undefined - this.poolConnected = undefined - - await this.indy.closePoolLedger(poolHandle) - } - - public async delete() { - // Close the pool if currently open - if (this._poolHandle) { - await this.close() - } - - await this.indy.deletePoolLedgerConfig(this.poolConfig.id) - } - - public async connect() { - if (!this.poolConnected) { - // Save the promise of connectToLedger to determine if we are done connecting - this.poolConnected = this.connectToLedger() - this.poolConnected.catch((error) => { - // Set poolConnected to undefined so we can retry connection upon failure - this.poolConnected = undefined - this.logger.error('Connection to pool: ' + this.poolConfig.genesisPath + ' failed.', { error }) - }) - return this.poolConnected - } else { - throw new AriesFrameworkError('Cannot attempt connection to ledger, already connecting.') - } - } - - private async connectToLedger() { - const poolName = this.poolConfig.id - const genesisPath = await this.getGenesisPath() - - if (!genesisPath) { - throw new AriesFrameworkError('Cannot connect to ledger without genesis file') - } - - this.logger.debug(`Connecting to ledger pool '${poolName}'`, { genesisPath }) - await this.indy.setProtocolVersion(2) - - try { - this._poolHandle = await this.indy.openPoolLedger(poolName) - return this._poolHandle - } catch (error) { - if (!isIndyError(error, 'PoolLedgerNotCreatedError')) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - this.logger.debug(`Pool '${poolName}' does not exist yet, creating.`, { - indyError: 'PoolLedgerNotCreatedError', - }) - try { - await this.indy.createPoolLedgerConfig(poolName, { genesis_txn: genesisPath }) - this._poolHandle = await this.indy.openPoolLedger(poolName) - return this._poolHandle - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async submitRequest(request: Indy.LedgerRequest) { - return this.indy.submitRequest(await this.getPoolHandle(), request) - } - - public async submitReadRequest(request: Indy.LedgerRequest) { - const response = await this.submitRequest(request) - - if (isLedgerRejectResponse(response) || isLedgerReqnackResponse(response)) { - throw new LedgerError(`Ledger '${this.id}' rejected read transaction request: ${response.reason}`) - } - - return response as Indy.LedgerReadReplyResponse - } - - public async submitWriteRequest(request: Indy.LedgerRequest) { - const response = await this.submitRequest(request) - - if (isLedgerRejectResponse(response) || isLedgerReqnackResponse(response)) { - throw new LedgerError(`Ledger '${this.id}' rejected write transaction request: ${response.reason}`) - } - - return response as Indy.LedgerWriteReplyResponse - } - - private async getPoolHandle() { - if (this.poolConnected) { - // If we have tried to already connect to pool wait for it - try { - await this.poolConnected - } catch (error) { - this.logger.error('Connection to pool: ' + this.poolConfig.genesisPath + ' failed.', { error }) - } - } - - if (!this._poolHandle) { - return this.connect() - } - - return this._poolHandle - } - - private async getGenesisPath() { - // If the path is already provided return it - if (this.poolConfig.genesisPath) return this.poolConfig.genesisPath - - // Determine the genesisPath - const genesisPath = this.fileSystem.basePath + `/afj/genesis-${this.poolConfig.id}.txn` - // Store genesis data if provided - if (this.poolConfig.genesisTransactions) { - await this.fileSystem.write(genesisPath, this.poolConfig.genesisTransactions) - this.poolConfig.genesisPath = genesisPath - return genesisPath - } - - // No genesisPath - return null - } -} - -export interface AuthorAgreement { - digest: string - version: string - text: string - ratification_ts: number - acceptanceMechanisms: AcceptanceMechanisms -} - -export interface AcceptanceMechanisms { - aml: Record - amlContext: string - version: string -} diff --git a/packages/core/src/modules/ledger/LedgerApi.ts b/packages/core/src/modules/ledger/LedgerApi.ts deleted file mode 100644 index bb836cf50d..0000000000 --- a/packages/core/src/modules/ledger/LedgerApi.ts +++ /dev/null @@ -1,217 +0,0 @@ -import type { IndyPoolConfig } from './IndyPool' -import type { SchemaTemplate, CredentialDefinitionTemplate } from './services' -import type { CredDef, NymRole, Schema } from 'indy-sdk' - -import { AgentContext } from '../../agent' -import { AriesFrameworkError } from '../../error' -import { IndySdkError } from '../../error/IndySdkError' -import { injectable } from '../../plugins' -import { isIndyError } from '../../utils/indyError' -import { - getLegacyCredentialDefinitionId, - getLegacySchemaId, - getQualifiedIndyCredentialDefinitionId, - getQualifiedIndySchemaId, -} from '../../utils/indyIdentifiers' -import { AnonCredsCredentialDefinitionRecord } from '../indy/repository/AnonCredsCredentialDefinitionRecord' -import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRecord } from '../indy/repository/AnonCredsSchemaRecord' -import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' - -import { LedgerModuleConfig } from './LedgerModuleConfig' -import { IndyLedgerService } from './services' - -@injectable() -export class LedgerApi { - public config: LedgerModuleConfig - - private ledgerService: IndyLedgerService - private agentContext: AgentContext - private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository - private anonCredsSchemaRepository: AnonCredsSchemaRepository - - public constructor( - ledgerService: IndyLedgerService, - agentContext: AgentContext, - anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository, - anonCredsSchemaRepository: AnonCredsSchemaRepository, - config: LedgerModuleConfig - ) { - this.ledgerService = ledgerService - this.agentContext = agentContext - this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository - this.anonCredsSchemaRepository = anonCredsSchemaRepository - this.config = config - } - - public setPools(poolConfigs: IndyPoolConfig[]) { - return this.ledgerService.setPools(poolConfigs) - } - - /** - * Connect to all the ledger pools - */ - public async connectToPools() { - await this.ledgerService.connectToPools() - } - - /** - * @deprecated use agent.dids.create instead - */ - public async registerPublicDid(did: string, verkey: string, alias: string, role?: NymRole) { - const myPublicDid = this.agentContext.wallet.publicDid?.did - - if (!myPublicDid) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - return this.ledgerService.registerPublicDid(this.agentContext, myPublicDid, did, verkey, alias, role) - } - - /** - * @deprecated use agent.dids.resolve instead - */ - public async getPublicDid(did: string) { - return this.ledgerService.getPublicDid(this.agentContext, did) - } - - public async getSchema(id: string) { - return this.ledgerService.getSchema(this.agentContext, id) - } - - public async registerSchema(schema: SchemaTemplate): Promise { - const did = this.agentContext.wallet.publicDid?.did - - if (!did) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - const schemaId = getLegacySchemaId(did, schema.name, schema.version) - - // Generate the qualified ID - const qualifiedIdentifier = getQualifiedIndySchemaId(this.ledgerService.getDidIndyWriteNamespace(), schemaId) - - // Try find the schema in the wallet - const schemaRecord = await this.anonCredsSchemaRepository.findById(this.agentContext, qualifiedIdentifier) - // Schema in wallet - if (schemaRecord) { - // Transform qualified to unqualified - return { - ...schemaRecord.schema, - id: schemaId, - } - } - - const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId) - - if (schemaFromLedger) return schemaFromLedger - const createdSchema = await this.ledgerService.registerSchema(this.agentContext, did, schema) - - const anonCredsSchema = new AnonCredsSchemaRecord({ - schema: { ...createdSchema, id: qualifiedIdentifier }, - }) - await this.anonCredsSchemaRepository.save(this.agentContext, anonCredsSchema) - - return createdSchema - } - - private async findBySchemaIdOnLedger(schemaId: string) { - try { - return await this.ledgerService.getSchema(this.agentContext, schemaId) - } catch (e) { - if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null - - throw e - } - } - - private async findByCredentialDefinitionIdOnLedger(credentialDefinitionId: string): Promise { - try { - return await this.ledgerService.getCredentialDefinition(this.agentContext, credentialDefinitionId) - } catch (e) { - if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null - - throw e - } - } - - public async registerCredentialDefinition( - credentialDefinitionTemplate: Omit - ) { - const did = this.agentContext.wallet.publicDid?.did - - if (!did) { - throw new AriesFrameworkError('Agent has no public DID.') - } - - // Construct credential definition ID - const credentialDefinitionId = getLegacyCredentialDefinitionId( - did, - credentialDefinitionTemplate.schema.seqNo, - credentialDefinitionTemplate.tag - ) - - // Construct qualified identifier - const qualifiedIdentifier = getQualifiedIndyCredentialDefinitionId( - this.ledgerService.getDidIndyWriteNamespace(), - credentialDefinitionId - ) - - // Check if the credential exists in wallet. If so, return it - const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findById( - this.agentContext, - qualifiedIdentifier - ) - - // Credential Definition in wallet - if (credentialDefinitionRecord) { - // Transform qualified to unqualified - return { - ...credentialDefinitionRecord.credentialDefinition, - id: credentialDefinitionId, - } - } - - // Check for the credential on the ledger. - const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId) - if (credentialDefinitionOnLedger) { - throw new AriesFrameworkError( - `No credential definition record found and credential definition ${credentialDefinitionId} already exists on the ledger.` - ) - } - - // Register the credential - const registeredDefinition = await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { - ...credentialDefinitionTemplate, - signatureType: 'CL', - }) - // Replace the unqualified with qualified Identifier in anonCred - const anonCredCredential = new AnonCredsCredentialDefinitionRecord({ - credentialDefinition: { ...registeredDefinition, id: qualifiedIdentifier }, - }) - await this.anonCredsCredentialDefinitionRepository.save(this.agentContext, anonCredCredential) - - return registeredDefinition - } - - public async getCredentialDefinition(id: string) { - return this.ledgerService.getCredentialDefinition(this.agentContext, id) - } - - public async getRevocationRegistryDefinition(revocationRegistryDefinitionId: string) { - return this.ledgerService.getRevocationRegistryDefinition(this.agentContext, revocationRegistryDefinitionId) - } - - public async getRevocationRegistryDelta( - revocationRegistryDefinitionId: string, - fromSeconds = 0, - toSeconds = new Date().getTime() - ) { - return this.ledgerService.getRevocationRegistryDelta( - this.agentContext, - revocationRegistryDefinitionId, - fromSeconds, - toSeconds - ) - } -} diff --git a/packages/core/src/modules/ledger/LedgerModule.ts b/packages/core/src/modules/ledger/LedgerModule.ts deleted file mode 100644 index 4090d146ab..0000000000 --- a/packages/core/src/modules/ledger/LedgerModule.ts +++ /dev/null @@ -1,37 +0,0 @@ -import type { LedgerModuleConfigOptions } from './LedgerModuleConfig' -import type { DependencyManager, Module } from '../../plugins' - -import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' - -import { LedgerApi } from './LedgerApi' -import { LedgerModuleConfig } from './LedgerModuleConfig' -import { IndyLedgerService, IndyPoolService } from './services' - -export class LedgerModule implements Module { - public readonly config: LedgerModuleConfig - public readonly api = LedgerApi - - public constructor(config?: LedgerModuleConfigOptions) { - this.config = new LedgerModuleConfig(config) - } - - /** - * Registers the dependencies of the ledger module on the dependency manager. - */ - public register(dependencyManager: DependencyManager) { - // Api - dependencyManager.registerContextScoped(LedgerApi) - - // Config - dependencyManager.registerInstance(LedgerModuleConfig, this.config) - - // Services - dependencyManager.registerSingleton(IndyLedgerService) - dependencyManager.registerSingleton(IndyPoolService) - - // Repositories - dependencyManager.registerSingleton(AnonCredsCredentialDefinitionRepository) - dependencyManager.registerSingleton(AnonCredsSchemaRepository) - } -} diff --git a/packages/core/src/modules/ledger/LedgerModuleConfig.ts b/packages/core/src/modules/ledger/LedgerModuleConfig.ts deleted file mode 100644 index 12c9d99fc0..0000000000 --- a/packages/core/src/modules/ledger/LedgerModuleConfig.ts +++ /dev/null @@ -1,43 +0,0 @@ -import type { IndyPoolConfig } from './IndyPool' - -/** - * LedgerModuleConfigOptions defines the interface for the options of the RecipientModuleConfig class. - * This can contain optional parameters that have default values in the config class itself. - */ -export interface LedgerModuleConfigOptions { - /** - * Whether to automatically connect to all {@link LedgerModuleConfigOptions.indyLedgers} on startup. - * This will be done asynchronously, so the initialization of the agent won't be impacted. However, - * this does mean there may be unneeded connections to the ledger. - * - * @default true - */ - connectToIndyLedgersOnStartup?: boolean - - /** - * Array of configurations of indy ledgers to connect to. Each item in the list must include either the `genesisPath` or `genesisTransactions` property. - * - * The first ledger in the list will be used for writing transactions to the ledger. - * - * @default [] - */ - indyLedgers?: IndyPoolConfig[] -} - -export class LedgerModuleConfig { - private options: LedgerModuleConfigOptions - - public constructor(options?: LedgerModuleConfigOptions) { - this.options = options ?? {} - } - - /** See {@link LedgerModuleConfigOptions.connectToIndyLedgersOnStartup} */ - public get connectToIndyLedgersOnStartup() { - return this.options.connectToIndyLedgersOnStartup ?? true - } - - /** See {@link LedgerModuleConfigOptions.indyLedgers} */ - public get indyLedgers() { - return this.options.indyLedgers ?? [] - } -} diff --git a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts deleted file mode 100644 index b34d2b6fcf..0000000000 --- a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts +++ /dev/null @@ -1,427 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { Cache } from '../../cache' -import type { IndyPoolConfig } from '../IndyPool' -import type { CachedDidResponse } from '../services/IndyPoolService' - -import { Subject } from 'rxjs' - -import { NodeFileSystem } from '../../../../../node/src/NodeFileSystem' -import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../tests/helpers' -import { SigningProviderRegistry } from '../../../crypto/signing-provider' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { IndyWallet } from '../../../wallet/IndyWallet' -import { CacheModuleConfig, InMemoryLruCache } from '../../cache' -import { LedgerError } from '../error/LedgerError' -import { LedgerNotConfiguredError } from '../error/LedgerNotConfiguredError' -import { LedgerNotFoundError } from '../error/LedgerNotFoundError' -import { IndyPoolService } from '../services/IndyPoolService' - -import { getDidResponsesForDid } from './didResponses' - -const pools: IndyPoolConfig[] = [ - { - id: 'sovrinMain', - indyNamespace: 'sovrin', - isProduction: true, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, - { - id: 'sovrinBuilder', - indyNamespace: 'sovrin:builder', - isProduction: false, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, - { - id: 'sovringStaging', - indyNamespace: 'sovrin:staging', - isProduction: false, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, - { - id: 'indicioMain', - indyNamespace: 'indicio', - isProduction: true, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, - { - id: 'bcovrinTest', - indyNamespace: 'bcovrin:test', - isProduction: false, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, -] - -describe('IndyPoolService', () => { - const config = getAgentConfig('IndyPoolServiceTest', { - indyLedgers: pools, - }) - let agentContext: AgentContext - let wallet: IndyWallet - let poolService: IndyPoolService - let cache: Cache - - beforeAll(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) - - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) - }) - - afterAll(async () => { - await wallet.delete() - }) - - beforeEach(async () => { - cache = new InMemoryLruCache({ limit: 200 }) - agentContext = getAgentContext({ - registerInstances: [[CacheModuleConfig, new CacheModuleConfig({ cache })]], - }) - poolService = new IndyPoolService(agentDependencies, config.logger, new Subject(), new NodeFileSystem()) - - poolService.setPools(pools) - }) - - describe('ledgerWritePool', () => { - it('should return the first pool', async () => { - expect(poolService.ledgerWritePool).toBe(poolService.pools[0]) - }) - - it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { - poolService.setPools([]) - - expect(() => poolService.ledgerWritePool).toThrow(LedgerNotConfiguredError) - }) - }) - - describe('getPoolForDid', () => { - it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { - poolService.setPools([]) - - expect(poolService.getPoolForDid(agentContext, 'some-did')).rejects.toThrow(LedgerNotConfiguredError) - }) - - it('should throw a LedgerError if all ledger requests throw an error other than NotFoundError', async () => { - const did = 'Y5bj4SjCiTM9PgeheKAiXx' - - poolService.pools.forEach((pool) => { - const spy = jest.spyOn(pool, 'submitReadRequest') - spy.mockImplementationOnce(() => Promise.reject(new AriesFrameworkError('Something went wrong'))) - }) - - expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(LedgerError) - }) - - it('should throw a LedgerNotFoundError if all pools did not find the did on the ledger', async () => { - const did = 'Y5bj4SjCiTM9PgeheKAiXx' - // Not found on any of the ledgers - const responses = getDidResponsesForDid(did, pools, {}) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'submitReadRequest') - spy.mockImplementationOnce(responses[index]) - }) - - expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(LedgerNotFoundError) - }) - - it('should return the pool if the did was only found on one ledger', async () => { - const did = 'TL1EaPFCZ8Si5aUrqScBDt' - // Only found on one ledger - const responses = getDidResponsesForDid(did, pools, { - sovrinMain: '~43X4NhAFqREffK7eWdKgFH', - }) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'submitReadRequest') - spy.mockImplementationOnce(responses[index]) - }) - - const { pool } = await poolService.getPoolForDid(agentContext, did) - - expect(pool.config.id).toBe('sovrinMain') - }) - - it('should return the first pool with a self certifying DID if at least one did is self certifying ', async () => { - const did = 'did:sov:q7ATwTYbQDgiigVijUAej' - // Found on one production and one non production ledger - const responses = getDidResponsesForDid(did, pools, { - indicioMain: '~43X4NhAFqREffK7eWdKgFH', - bcovrinTest: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', - sovrinBuilder: '~43X4NhAFqREffK7eWdKgFH', - }) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'submitReadRequest') - spy.mockImplementationOnce(responses[index]) - }) - - const { pool } = await poolService.getPoolForDid(agentContext, did) - - expect(pool.config.id).toBe('sovrinBuilder') - }) - - it('should return the production pool if the did was found on one production and one non production ledger and both DIDs are not self certifying', async () => { - const did = 'V6ty6ttM3EjuCtosH6sGtW' - // Found on one production and one non production ledger - const responses = getDidResponsesForDid(did, pools, { - indicioMain: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', - sovrinBuilder: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', - }) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'submitReadRequest') - spy.mockImplementationOnce(responses[index]) - }) - - const { pool } = await poolService.getPoolForDid(agentContext, did) - - expect(pool.config.id).toBe('indicioMain') - }) - - it('should return the pool with the self certified did if the did was found on two production ledgers where one did is self certified', async () => { - const did = 'VsKV7grR1BUE29mG2Fm2kX' - // Found on two production ledgers. Sovrin is self certified - const responses = getDidResponsesForDid(did, pools, { - sovrinMain: '~43X4NhAFqREffK7eWdKgFH', - indicioMain: 'kqa2HyagzfMAq42H5f9u3UMwnSBPQx2QfrSyXbUPxMn', - }) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'submitReadRequest') - spy.mockImplementationOnce(responses[index]) - }) - - const { pool } = await poolService.getPoolForDid(agentContext, did) - - expect(pool.config.id).toBe('sovrinMain') - }) - - it('should return the first pool with a self certified did if the did was found on three non production ledgers where two DIDs are self certified', async () => { - const did = 'HEi9QViXNThGQaDsQ3ptcw' - // Found on two non production ledgers. Sovrin is self certified - const responses = getDidResponsesForDid(did, pools, { - sovrinBuilder: '~M9kv2Ez61cur7X39DXWh8W', - sovrinStaging: '~M9kv2Ez61cur7X39DXWh8W', - bcovrinTest: '3SeuRm3uYuQDYmHeuMLu1xNHozNTtzS3kbZRFMMCWrX4', - }) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'submitReadRequest') - spy.mockImplementationOnce(responses[index]) - }) - - const { pool } = await poolService.getPoolForDid(agentContext, did) - - expect(pool.config.id).toBe('sovrinBuilder') - }) - - it('should return the pool from the cache if the did was found in the cache', async () => { - const did = 'HEi9QViXNThGQaDsQ3ptcw' - - const expectedPool = pools[3] - - const didResponse: CachedDidResponse = { - nymResponse: { - did, - role: 'ENDORSER', - verkey: '~M9kv2Ez61cur7X39DXWh8W', - }, - poolId: expectedPool.id, - } - - await cache.set(agentContext, `IndySdkPoolService:${did}`, didResponse) - const { pool } = await poolService.getPoolForDid(agentContext, did) - - expect(pool.config.id).toBe(pool.id) - }) - - it('should set the poolId in the cache if the did was not found in the cache, but resolved later on', async () => { - const did = 'HEi9QViXNThGQaDsQ3ptcw' - // Found on one ledger - const responses = getDidResponsesForDid(did, pools, { - sovrinBuilder: '~M9kv2Ez61cur7X39DXWh8W', - }) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'submitReadRequest') - spy.mockImplementationOnce(responses[index]) - }) - - const { pool } = await poolService.getPoolForDid(agentContext, did) - - expect(pool.config.id).toBe('sovrinBuilder') - expect(pool.config.indyNamespace).toBe('sovrin:builder') - - expect(await cache.get(agentContext, `IndySdkPoolService:${did}`)).toEqual({ - nymResponse: { - did, - verkey: '~M9kv2Ez61cur7X39DXWh8W', - role: '0', - }, - poolId: 'sovrinBuilder', - }) - }) - }) - - describe('getPoolForNamespace', () => { - it('should throw a LedgerNotConfiguredError error if no pools are configured on the pool service', async () => { - poolService.setPools([]) - - expect(() => poolService.getPoolForNamespace()).toThrow(LedgerNotConfiguredError) - }) - - it('should return the first pool if indyNamespace is not provided', async () => { - const expectedPool = pools[0] - - expect(poolService.getPoolForNamespace().id).toEqual(expectedPool.id) - }) - - it('should throw a LedgerNotFoundError error if any of the pools did not have the provided indyNamespace', async () => { - const indyNameSpace = 'test' - const responses = pools.map((pool) => pool.indyNamespace) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'didIndyNamespace', 'get') - spy.mockReturnValueOnce(responses[index]) - }) - - expect(() => poolService.getPoolForNamespace(indyNameSpace)).toThrow(LedgerNotFoundError) - }) - - it('should return the first pool that indyNamespace matches', async () => { - const expectedPool = pools[3] - const indyNameSpace = 'indicio' - const responses = pools.map((pool) => pool.indyNamespace) - - poolService.pools.forEach((pool, index) => { - const spy = jest.spyOn(pool, 'didIndyNamespace', 'get') - spy.mockReturnValueOnce(responses[index]) - }) - - const pool = poolService.getPoolForNamespace(indyNameSpace) - - expect(pool.id).toEqual(expectedPool.id) - }) - }) - - describe('submitWriteRequest', () => { - it('should throw an error if the config version does not match', async () => { - const pool = poolService.getPoolForNamespace() - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ - digest: 'abcde', - version: '2.0', - text: 'jhsdhbv', - ratification_ts: 12345678, - acceptanceMechanisms: { - aml: { accept: 'accept' }, - amlContext: 'accept', - version: '3', - }, - } as never) - await expect( - poolService.submitWriteRequest( - agentContext, - pool, - { - reqId: 1668174449192969000, - identifier: 'BBPoJqRKatdcfLEAFL7exC', - operation: { - type: '1', - dest: 'N8NQHLtCKfPmWMgCSdfa7h', - verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', - alias: 'Heinz57', - }, - protocolVersion: 2, - }, - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' - ) - ).rejects.toThrowError( - 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["accept"] and version 2.0 in pool.' - ) - }) - - it('should throw an error if the config acceptance mechanism does not match', async () => { - const pool = poolService.getPoolForNamespace() - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ - digest: 'abcde', - version: '1.0', - text: 'jhsdhbv', - ratification_ts: 12345678, - acceptanceMechanisms: { - aml: { decline: 'accept' }, - amlContext: 'accept', - version: '1', - }, - } as never) - await expect( - poolService.submitWriteRequest( - agentContext, - pool, - { - reqId: 1668174449192969000, - identifier: 'BBPoJqRKatdcfLEAFL7exC', - operation: { - type: '1', - dest: 'N8NQHLtCKfPmWMgCSdfa7h', - verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', - alias: 'Heinz57', - }, - protocolVersion: 2, - }, - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' - ) - ).rejects.toThrowError( - 'Unable to satisfy matching TAA with mechanism "accept" and version "1" in pool.\n Found ["decline"] and version 1.0 in pool.' - ) - }) - - it('should throw an error if no config is present', async () => { - const pool = poolService.getPoolForNamespace() - pool.authorAgreement = undefined - pool.config.transactionAuthorAgreement = undefined - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - jest.spyOn(poolService, 'getTransactionAuthorAgreement').mockResolvedValue({ - digest: 'abcde', - version: '1.0', - text: 'jhsdhbv', - ratification_ts: 12345678, - acceptanceMechanisms: { - aml: { accept: 'accept' }, - amlContext: 'accept', - version: '3', - }, - } as never) - await expect( - poolService.submitWriteRequest( - agentContext, - pool, - { - reqId: 1668174449192969000, - identifier: 'BBPoJqRKatdcfLEAFL7exC', - operation: { - type: '1', - dest: 'N8NQHLtCKfPmWMgCSdfa7h', - verkey: 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf', - alias: 'Heinz57', - }, - protocolVersion: 2, - }, - 'GAb4NUvpBcHVCvtP45vTVa5Bp74vFg3iXzdp1Gbd68Wf' - ) - ).rejects.toThrowError(/Please, specify a transaction author agreement with version and acceptance mechanism/) - }) - }) -}) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts deleted file mode 100644 index cf7b35bbc5..0000000000 --- a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts +++ /dev/null @@ -1,399 +0,0 @@ -import type { AgentContext } from '../../../agent/context/AgentContext' -import type { IndyPoolConfig } from '../IndyPool' -import type { CredentialDefinitionTemplate } from '../services/IndyLedgerService' -import type * as Indy from 'indy-sdk' - -import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' -import { SigningProviderRegistry } from '../../../crypto/signing-provider' -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' -import { getLegacySchemaId, getLegacyCredentialDefinitionId } from '../../../utils' -import { IndyWallet } from '../../../wallet/IndyWallet' -import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' -import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRecord } from '../../indy/repository/AnonCredsSchemaRecord' -import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' -import { LedgerApi } from '../LedgerApi' -import { LedgerModuleConfig } from '../LedgerModuleConfig' -import { IndyLedgerService } from '../services/IndyLedgerService' - -jest.mock('../services/IndyLedgerService') -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock - -jest.mock('../../indy/repository/AnonCredsCredentialDefinitionRepository') -const AnonCredsCredentialDefinitionRepositoryMock = - AnonCredsCredentialDefinitionRepository as jest.Mock -jest.mock('../../indy/repository/AnonCredsSchemaRepository') -const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock - -const did = 'Y5bj4SjCiTM9PgeheKAiXx' - -const schemaId = 'Y5bj4SjCiTM9PgeheKAiXx:2:awesomeSchema:1' - -const schema: Indy.Schema = { - id: schemaId, - attrNames: ['hello', 'world'], - name: 'awesomeSchema', - version: '1', - ver: '1', - seqNo: 99, -} - -const credentialDefinition = { - schema: schema, - tag: 'someTag', - signatureType: 'CL', - supportRevocation: true, -} - -const schemaIdQualified = 'did:indy:sovrin:Y5bj4SjCiTM9PgeheKAiXx/anoncreds/v0/SCHEMA/awesomeSchema/1' -const schemaIdGenerated = getLegacySchemaId(did, schema.name, schema.version) -const qualifiedDidCred = 'did:indy:sovrin:Y5bj4SjCiTM9PgeheKAiXx/anoncreds/v0/CLAIM_DEF/99/someTag' - -const credDef: Indy.CredDef = { - id: qualifiedDidCred, - schemaId: schemaIdQualified, - type: 'CL', - tag: 'someTag', - value: { - primary: credentialDefinition as Record, - revocation: true, - }, - ver: '1', -} - -const credentialDefinitionTemplate: Omit = { - schema: { ...schema, id: schemaIdQualified }, - tag: 'someTag', - supportRevocation: true, -} - -const revocRegDef: Indy.RevocRegDef = { - id: 'abcde', - revocDefType: 'CL_ACCUM', - tag: 'someTag', - credDefId: 'abcde', - value: { - issuanceType: 'ISSUANCE_BY_DEFAULT', - maxCredNum: 3, - tailsHash: 'abcde', - tailsLocation: 'xyz', - publicKeys: { - accumKey: { - z: 'z', - }, - }, - }, - ver: 'abcde', -} - -const credentialDefinitionId = getLegacyCredentialDefinitionId( - did, - credentialDefinitionTemplate.schema.seqNo, - credentialDefinitionTemplate.tag -) - -const pools: IndyPoolConfig[] = [ - { - id: '7Tqg6BwSSWapxgUDm9KKgg', - indyNamespace: 'sovrin', - isProduction: true, - genesisTransactions: 'xxx', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, -] - -describe('LedgerApi', () => { - let wallet: IndyWallet - let ledgerService: IndyLedgerService - let anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository - let anonCredsSchemaRepository: AnonCredsSchemaRepository - let ledgerApi: LedgerApi - let agentContext: AgentContext - - const contextCorrelationId = 'mock' - const agentConfig = getAgentConfig('LedgerApiTest', { - indyLedgers: pools, - }) - - beforeEach(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) - }) - - afterEach(async () => { - await wallet.delete() - }) - - beforeEach(async () => { - ledgerService = new IndyLedgerServiceMock() - - agentContext = getAgentContext({ - wallet, - agentConfig, - contextCorrelationId, - }) - - anonCredsCredentialDefinitionRepository = new AnonCredsCredentialDefinitionRepositoryMock() - anonCredsSchemaRepository = new AnonCredsSchemaRepositoryMock() - - ledgerApi = new LedgerApi( - ledgerService, - agentContext, - anonCredsCredentialDefinitionRepository, - anonCredsSchemaRepository, - new LedgerModuleConfig() - ) - }) - - describe('LedgerApi', () => { - // Connect to pools - describe('connectToPools', () => { - it('should connect to all pools', async () => { - mockFunction(ledgerService.connectToPools).mockResolvedValue([1, 2, 4]) - await expect(ledgerApi.connectToPools()).resolves.toBeUndefined() - expect(ledgerService.connectToPools).toHaveBeenCalled() - }) - }) - - // Register public did - describe('registerPublicDid', () => { - it('should register a public DID', async () => { - mockFunction(ledgerService.registerPublicDid).mockResolvedValueOnce(did) - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - await expect(ledgerApi.registerPublicDid(did, 'abcde', 'someAlias')).resolves.toEqual(did) - expect(ledgerService.registerPublicDid).toHaveBeenCalledWith( - agentContext, - did, - did, - 'abcde', - 'someAlias', - undefined - ) - }) - - it('should throw an error if the DID cannot be registered because there is no public did', async () => { - const did = 'Y5bj4SjCiTM9PgeheKAiXx' - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerApi.registerPublicDid(did, 'abcde', 'someAlias')).rejects.toThrowError(AriesFrameworkError) - }) - }) - - // Get public DID - describe('getPublicDid', () => { - it('should return the public DID if there is one', async () => { - const nymResponse: Indy.GetNymResponse = { did: 'Y5bj4SjCiTM9PgeheKAiXx', verkey: 'abcde', role: 'STEWARD' } - mockProperty(wallet, 'publicDid', { did: nymResponse.did, verkey: nymResponse.verkey }) - mockFunction(ledgerService.getPublicDid).mockResolvedValueOnce(nymResponse) - await expect(ledgerApi.getPublicDid(nymResponse.did)).resolves.toEqual(nymResponse) - expect(ledgerService.getPublicDid).toHaveBeenCalledWith(agentContext, nymResponse.did) - }) - }) - - // Get schema - describe('getSchema', () => { - it('should return the schema by id if there is one', async () => { - mockFunction(ledgerService.getSchema).mockResolvedValueOnce(schema) - await expect(ledgerApi.getSchema(schemaId)).resolves.toEqual(schema) - expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) - }) - - it('should throw an error if no schema for the id exists', async () => { - mockFunction(ledgerService.getSchema).mockRejectedValueOnce( - new AriesFrameworkError('Error retrieving schema abcd from ledger 1') - ) - await expect(ledgerApi.getSchema(schemaId)).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getSchema).toHaveBeenCalledWith(agentContext, schemaId) - }) - }) - - describe('registerSchema', () => { - it('should throw an error if there is no public DID', async () => { - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).rejects.toThrowError( - AriesFrameworkError - ) - }) - - it('should return the schema from anonCreds when it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(anonCredsSchemaRepository.findById).mockResolvedValueOnce( - new AnonCredsSchemaRecord({ schema: { ...schema, id: schemaIdQualified } }) - ) - mockFunction(ledgerService.getDidIndyWriteNamespace).mockReturnValueOnce(pools[0].indyNamespace) - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { id, ...schemaWithoutId } = schema - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toMatchObject({ - ...schema, - id: schema.id, - }) - expect(anonCredsSchemaRepository.findById).toHaveBeenCalledWith(agentContext, schemaIdQualified) - }) - - it('should return the schema from the ledger when it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - jest - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger') - .mockResolvedValueOnce(new AnonCredsSchemaRecord({ schema: schema })) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toHaveProperty( - 'schema', - { ...schema } - ) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(jest.spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger')).toHaveBeenCalledWith(schemaIdGenerated) - }) - - it('should return the schema after registering it', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.registerSchema).mockResolvedValueOnce(schema) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual(schema) - expect(ledgerService.registerSchema).toHaveBeenCalledWith(agentContext, did, { - ...schema, - attributes: ['hello', 'world'], - }) - }) - }) - - describe('registerCredentialDefinition', () => { - it('should throw an error if there si no public DID', async () => { - mockProperty(wallet, 'publicDid', undefined) - await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( - AriesFrameworkError - ) - }) - - it('should return the credential definition from the wallet if it already exists', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - const anonCredsCredentialDefinitionRecord: AnonCredsCredentialDefinitionRecord = - new AnonCredsCredentialDefinitionRecord({ - credentialDefinition: credDef, - }) - mockFunction(anonCredsCredentialDefinitionRepository.findById).mockResolvedValueOnce( - anonCredsCredentialDefinitionRecord - ) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - mockFunction(ledgerService.getDidIndyWriteNamespace).mockReturnValueOnce(pools[0].indyNamespace) - await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toHaveProperty( - 'value.primary', - credentialDefinition - ) - expect(anonCredsCredentialDefinitionRepository.findById).toHaveBeenCalledWith(agentContext, qualifiedDidCred) - }) - - it('should throw an exception if the definition already exists on the ledger', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - jest - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .spyOn(LedgerApi.prototype as any, 'findByCredentialDefinitionIdOnLedger') - .mockResolvedValueOnce({ credentialDefinition: credentialDefinition }) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( - AriesFrameworkError - ) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - expect(jest.spyOn(LedgerApi.prototype as any, 'findByCredentialDefinitionIdOnLedger')).toHaveBeenCalledWith( - credentialDefinitionId - ) - }) - - it('should register the credential successfully if it is neither in the wallet and neither on the ledger', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.registerCredentialDefinition).mockResolvedValueOnce(credDef) - mockProperty(ledgerApi, 'config', { - connectToIndyLedgersOnStartup: true, - indyLedgers: pools, - } as LedgerModuleConfig) - await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toEqual(credDef) - expect(ledgerService.registerCredentialDefinition).toHaveBeenCalledWith(agentContext, did, { - ...credentialDefinitionTemplate, - signatureType: 'CL', - }) - }) - }) - - describe('getCredentialDefinition', () => { - it('should return the credential definition given the id', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.getCredentialDefinition).mockResolvedValue(credDef) - await expect(ledgerApi.getCredentialDefinition(credDef.id)).resolves.toEqual(credDef) - expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) - }) - - it('should throw an error if there is no credential definition for the given id', async () => { - mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(ledgerService.getCredentialDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerApi.getCredentialDefinition(credDef.id)).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getCredentialDefinition).toHaveBeenCalledWith(agentContext, credDef.id) - }) - }) - - describe('getRevocationRegistryDefinition', () => { - it('should return the ParseRevocationRegistryDefinitionTemplate for a valid revocationRegistryDefinitionId', async () => { - const parseRevocationRegistryDefinitionTemplate = { - revocationRegistryDefinition: revocRegDef, - revocationRegistryDefinitionTxnTime: 12345678, - } - mockFunction(ledgerService.getRevocationRegistryDefinition).mockResolvedValue( - parseRevocationRegistryDefinitionTemplate - ) - await expect(ledgerApi.getRevocationRegistryDefinition(revocRegDef.id)).resolves.toBe( - parseRevocationRegistryDefinitionTemplate - ) - expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenLastCalledWith(agentContext, revocRegDef.id) - }) - - it('should throw an error if the ParseRevocationRegistryDefinitionTemplate does not exists', async () => { - mockFunction(ledgerService.getRevocationRegistryDefinition).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerApi.getRevocationRegistryDefinition('abcde')).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getRevocationRegistryDefinition).toHaveBeenCalledWith(agentContext, revocRegDef.id) - }) - }) - - describe('getRevocationRegistryDelta', () => { - it('should return the ParseRevocationRegistryDeltaTemplate', async () => { - const revocRegDelta = { - value: { - prevAccum: 'prev', - accum: 'accum', - issued: [1, 2, 3], - revoked: [4, 5, 6], - }, - ver: 'ver', - } - const parseRevocationRegistryDeltaTemplate = { - revocationRegistryDelta: revocRegDelta, - deltaTimestamp: 12345678, - } - - mockFunction(ledgerService.getRevocationRegistryDelta).mockResolvedValueOnce( - parseRevocationRegistryDeltaTemplate - ) - await expect(ledgerApi.getRevocationRegistryDelta('12345')).resolves.toEqual( - parseRevocationRegistryDeltaTemplate - ) - expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) - }) - - it('should throw an error if the delta cannot be obtained', async () => { - mockFunction(ledgerService.getRevocationRegistryDelta).mockRejectedValueOnce(new AriesFrameworkError('')) - await expect(ledgerApi.getRevocationRegistryDelta('abcde1234')).rejects.toThrowError(AriesFrameworkError) - expect(ledgerService.getRevocationRegistryDelta).toHaveBeenCalledTimes(1) - }) - }) - }) -}) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts deleted file mode 100644 index b258bd5416..0000000000 --- a/packages/core/src/modules/ledger/__tests__/LedgerModule.test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { DependencyManager } from '../../../plugins/DependencyManager' -import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' -import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' -import { LedgerApi } from '../LedgerApi' -import { LedgerModule } from '../LedgerModule' -import { IndyLedgerService, IndyPoolService } from '../services' - -jest.mock('../../../plugins/DependencyManager') -const DependencyManagerMock = DependencyManager as jest.Mock - -const dependencyManager = new DependencyManagerMock() - -describe('LedgerModule', () => { - test('registers dependencies on the dependency manager', () => { - new LedgerModule().register(dependencyManager) - - expect(dependencyManager.registerContextScoped).toHaveBeenCalledTimes(1) - expect(dependencyManager.registerContextScoped).toHaveBeenCalledWith(LedgerApi) - - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(4) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyLedgerService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(IndyPoolService) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsCredentialDefinitionRepository) - expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsSchemaRepository) - }) -}) diff --git a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts deleted file mode 100644 index ec33976a63..0000000000 --- a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import type { LedgerRejectResponse, LedgerReqnackResponse } from 'indy-sdk' - -import * as LedgerUtil from '../ledgerUtil' - -describe('LedgerUtils', () => { - // IsLedgerRejectResponse - it('Should return true if the response op is: REJECT', () => { - const ledgerResponse: LedgerRejectResponse = { - op: 'REJECT', - reqId: 1, - reason: 'Why not', - identifier: '123456', - } - expect(LedgerUtil.isLedgerRejectResponse(ledgerResponse)).toEqual(true) - }) - it('Should return false if the response op is not: REJECT', () => { - const ledgerResponse: LedgerReqnackResponse = { - op: 'REQNACK', - reqId: 1, - reason: 'Why not', - identifier: '123456', - } - expect(LedgerUtil.isLedgerRejectResponse(ledgerResponse)).toEqual(false) - }) - - // isLedgerReqnackResponse - it('Should return true if the response op is: REQNACK', () => { - const ledgerResponse: LedgerReqnackResponse = { - op: 'REQNACK', - reqId: 1, - reason: 'Why not', - identifier: '123456', - } - expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(true) - }) - it('Should return false if the response op is NOT: REQNACK', () => { - const ledgerResponse: LedgerRejectResponse = { - op: 'REJECT', - reqId: 1, - reason: 'Why not', - identifier: '123456', - } - expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(false) - }) -}) diff --git a/packages/core/src/modules/ledger/error/LedgerError.ts b/packages/core/src/modules/ledger/error/LedgerError.ts deleted file mode 100644 index 1ee8589cf9..0000000000 --- a/packages/core/src/modules/ledger/error/LedgerError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { AriesFrameworkError } from '../../../error/AriesFrameworkError' - -export class LedgerError extends AriesFrameworkError { - public constructor(message: string, { cause }: { cause?: Error } = {}) { - super(message, { cause }) - } -} diff --git a/packages/core/src/modules/ledger/error/LedgerNotConfiguredError.ts b/packages/core/src/modules/ledger/error/LedgerNotConfiguredError.ts deleted file mode 100644 index 0cee3914dc..0000000000 --- a/packages/core/src/modules/ledger/error/LedgerNotConfiguredError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LedgerError } from './LedgerError' - -export class LedgerNotConfiguredError extends LedgerError { - public constructor(message: string, { cause }: { cause?: Error } = {}) { - super(message, { cause }) - } -} diff --git a/packages/core/src/modules/ledger/error/LedgerNotFoundError.ts b/packages/core/src/modules/ledger/error/LedgerNotFoundError.ts deleted file mode 100644 index 09355964d6..0000000000 --- a/packages/core/src/modules/ledger/error/LedgerNotFoundError.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { LedgerError } from './LedgerError' - -export class LedgerNotFoundError extends LedgerError { - public constructor(message: string, { cause }: { cause?: Error } = {}) { - super(message, { cause }) - } -} diff --git a/packages/core/src/modules/ledger/error/index.ts b/packages/core/src/modules/ledger/error/index.ts deleted file mode 100644 index 79c42fc2b6..0000000000 --- a/packages/core/src/modules/ledger/error/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './LedgerError' -export * from './LedgerNotConfiguredError' -export * from './LedgerNotFoundError' diff --git a/packages/core/src/modules/ledger/index.ts b/packages/core/src/modules/ledger/index.ts deleted file mode 100644 index fc65f390db..0000000000 --- a/packages/core/src/modules/ledger/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './services' -export * from './LedgerApi' -export * from './IndyPool' -export * from './LedgerModule' diff --git a/packages/core/src/modules/ledger/ledgerUtil.ts b/packages/core/src/modules/ledger/ledgerUtil.ts deleted file mode 100644 index 62e75f1e72..0000000000 --- a/packages/core/src/modules/ledger/ledgerUtil.ts +++ /dev/null @@ -1,9 +0,0 @@ -import type * as Indy from 'indy-sdk' - -export function isLedgerRejectResponse(response: Indy.LedgerResponse): response is Indy.LedgerRejectResponse { - return response.op === 'REJECT' -} - -export function isLedgerReqnackResponse(response: Indy.LedgerResponse): response is Indy.LedgerReqnackResponse { - return response.op === 'REQNACK' -} diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts deleted file mode 100644 index df5e535d6d..0000000000 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ /dev/null @@ -1,503 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { IndyPoolConfig } from '../IndyPool' -import type { CredDef, default as Indy, NymRole, Schema } from 'indy-sdk' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger' -import { injectable, inject } from '../../../plugins' -import { - didFromCredentialDefinitionId, - didFromRevocationRegistryDefinitionId, - didFromSchemaId, -} from '../../../utils/did' -import { isIndyError } from '../../../utils/indyError' -import { IndyIssuerService } from '../../indy/services/IndyIssuerService' - -import { IndyPoolService } from './IndyPoolService' - -@injectable() -export class IndyLedgerService { - private indy: typeof Indy - private logger: Logger - - private indyIssuer: IndyIssuerService - private indyPoolService: IndyPoolService - - public constructor( - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger, - indyIssuer: IndyIssuerService, - indyPoolService: IndyPoolService - ) { - this.indy = agentDependencies.indy - this.logger = logger - this.indyIssuer = indyIssuer - this.indyPoolService = indyPoolService - } - - public setPools(poolConfigs: IndyPoolConfig[]) { - return this.indyPoolService.setPools(poolConfigs) - } - - /** - * @deprecated - */ - public getDidIndyWriteNamespace(): string { - return this.indyPoolService.ledgerWritePool.config.indyNamespace - } - - public async connectToPools() { - return this.indyPoolService.connectToPools() - } - - /** - * @deprecated - */ - public async registerPublicDid( - agentContext: AgentContext, - submitterDid: string, - targetDid: string, - verkey: string, - alias: string, - role?: NymRole - ) { - const pool = this.indyPoolService.getPoolForNamespace() - - try { - this.logger.debug(`Register public did '${targetDid}' on ledger '${pool.id}'`) - - const request = await this.indy.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) - - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) - - this.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.id}'`, { - response, - }) - - return targetDid - } catch (error) { - this.logger.error(`Error registering public did '${targetDid}' on ledger '${pool.id}'`, { - error, - submitterDid, - targetDid, - verkey, - alias, - role, - pool: pool.id, - }) - - throw error - } - } - - /** - * @deprecated - */ - public async getPublicDid(agentContext: AgentContext, did: string) { - // Getting the pool for a did also retrieves the DID. We can just use that - const { did: didResponse } = await this.indyPoolService.getPoolForDid(agentContext, did) - - return didResponse - } - - /** - * @deprecated - */ - public async setEndpointsForDid( - agentContext: AgentContext, - did: string, - endpoints: IndyEndpointAttrib - ): Promise { - const pool = this.indyPoolService.getPoolForNamespace() - - try { - this.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.id}'`, endpoints) - - const request = await this.indy.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) - - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) - this.logger.debug(`Successfully set endpoints for did '${did}' on ledger '${pool.id}'`, { - response, - endpoints, - }) - } catch (error) { - this.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.id}'`, { - error, - did, - endpoints, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** - * @deprecated - */ - public async getEndpointsForDid(agentContext: AgentContext, did: string) { - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - try { - this.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.id}'`) - - const request = await this.indy.buildGetAttribRequest(null, did, 'endpoint', null, null) - - this.logger.debug(`Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.id}'`) - const response = await this.indyPoolService.submitReadRequest(pool, request) - - if (!response.result.data) return {} - - const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib - this.logger.debug(`Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.id}'`, { - response, - endpoints, - }) - - return endpoints ?? {} - } catch (error) { - this.logger.error(`Error retrieving endpoints for did '${did}' from ledger '${pool.id}'`, { - error, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async registerSchema( - agentContext: AgentContext, - did: string, - schemaTemplate: SchemaTemplate - ): Promise { - const pool = this.indyPoolService.getPoolForNamespace() - - try { - this.logger.debug(`Register schema on ledger '${pool.id}' with did '${did}'`, schemaTemplate) - const { name, attributes, version } = schemaTemplate - const schema = await this.indyIssuer.createSchema(agentContext, { originDid: did, name, version, attributes }) - - const request = await this.indy.buildSchemaRequest(did, schema) - - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) - this.logger.debug(`Registered schema '${schema.id}' on ledger '${pool.id}'`, { - response, - schema, - }) - - schema.seqNo = response.result.txnMetadata.seqNo - - return schema - } catch (error) { - this.logger.error(`Error registering schema for did '${did}' on ledger '${pool.id}'`, { - error, - did, - schemaTemplate, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async getSchema(agentContext: AgentContext, schemaId: string) { - const did = didFromSchemaId(schemaId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - try { - this.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.id}'`) - - const request = await this.indy.buildGetSchemaRequest(null, schemaId) - - this.logger.trace(`Submitting get schema request for schema '${schemaId}' to ledger '${pool.id}'`) - const response = await this.indyPoolService.submitReadRequest(pool, request) - - this.logger.trace(`Got un-parsed schema '${schemaId}' from ledger '${pool.id}'`, { - response, - }) - - const [, schema] = await this.indy.parseGetSchemaResponse(response) - this.logger.debug(`Got schema '${schemaId}' from ledger '${pool.id}'`, { - schema, - }) - - return schema - } catch (error) { - this.logger.error(`Error retrieving schema '${schemaId}' from ledger '${pool.id}'`, { - error, - schemaId, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async registerCredentialDefinition( - agentContext: AgentContext, - did: string, - credentialDefinitionTemplate: CredentialDefinitionTemplate - ): Promise { - const pool = this.indyPoolService.getPoolForNamespace() - - try { - this.logger.debug( - `Registering credential definition on ledger '${pool.id}' with did '${did}'`, - credentialDefinitionTemplate - ) - const { schema, tag, signatureType, supportRevocation } = credentialDefinitionTemplate - - const credentialDefinition = await this.indyIssuer.createCredentialDefinition(agentContext, { - issuerDid: did, - schema, - tag, - signatureType, - supportRevocation, - }) - - const request = await this.indy.buildCredDefRequest(did, credentialDefinition) - - const response = await this.indyPoolService.submitWriteRequest(agentContext, pool, request, did) - - this.logger.debug(`Registered credential definition '${credentialDefinition.id}' on ledger '${pool.id}'`, { - response, - credentialDefinition: credentialDefinition, - }) - - return credentialDefinition - } catch (error) { - this.logger.error( - `Error registering credential definition for schema '${credentialDefinitionTemplate.schema.id}' on ledger '${pool.id}'`, - { - error, - did, - credentialDefinitionTemplate, - } - ) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async getCredentialDefinition(agentContext: AgentContext, credentialDefinitionId: string) { - const did = didFromCredentialDefinitionId(credentialDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - this.logger.debug(`Using ledger '${pool.id}' to retrieve credential definition '${credentialDefinitionId}'`) - - try { - const request = await this.indy.buildGetCredDefRequest(null, credentialDefinitionId) - - this.logger.trace( - `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.id}'` - ) - - const response = await this.indyPoolService.submitReadRequest(pool, request) - this.logger.trace(`Got un-parsed credential definition '${credentialDefinitionId}' from ledger '${pool.id}'`, { - response, - }) - - const [, credentialDefinition] = await this.indy.parseGetCredDefResponse(response) - this.logger.debug(`Got credential definition '${credentialDefinitionId}' from ledger '${pool.id}'`, { - credentialDefinition, - }) - - return credentialDefinition - } catch (error) { - this.logger.error(`Error retrieving credential definition '${credentialDefinitionId}' from ledger '${pool.id}'`, { - error, - credentialDefinitionId, - pool: pool.id, - }) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async getRevocationRegistryDefinition( - agentContext: AgentContext, - revocationRegistryDefinitionId: string - ): Promise { - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - this.logger.debug( - `Using ledger '${pool.id}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` - ) - try { - //TODO - implement a cache - this.logger.trace( - `Revocation Registry Definition '${revocationRegistryDefinitionId}' not cached, retrieving from ledger` - ) - - const request = await this.indy.buildGetRevocRegDefRequest(null, revocationRegistryDefinitionId) - - this.logger.trace( - `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` - ) - const response = await this.indyPoolService.submitReadRequest(pool, request) - this.logger.trace( - `Got un-parsed revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.id}'`, - { - response, - } - ) - - const [, revocationRegistryDefinition] = await this.indy.parseGetRevocRegDefResponse(response) - - this.logger.debug(`Got revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, { - revocationRegistryDefinition, - }) - - return { revocationRegistryDefinition, revocationRegistryDefinitionTxnTime: response.result.txnTime } - } catch (error) { - this.logger.error( - `Error retrieving revocation registry definition '${revocationRegistryDefinitionId}' from ledger`, - { - error, - revocationRegistryDefinitionId: revocationRegistryDefinitionId, - pool: pool.id, - } - ) - throw error - } - } - - // Retrieves the accumulated state of a revocation registry by id given a revocation interval from & to (used primarily for proof creation) - public async getRevocationRegistryDelta( - agentContext: AgentContext, - revocationRegistryDefinitionId: string, - to: number = new Date().getTime(), - from = 0 - ): Promise { - //TODO - implement a cache - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - this.logger.debug( - `Using ledger '${pool.id}' to retrieve revocation registry delta with revocation registry definition id: '${revocationRegistryDefinitionId}'`, - { - to, - from, - } - ) - - try { - const request = await this.indy.buildGetRevocRegDeltaRequest(null, revocationRegistryDefinitionId, from, to) - - this.logger.trace( - `Submitting get revocation registry delta request for revocation registry '${revocationRegistryDefinitionId}' to ledger` - ) - - const response = await this.indyPoolService.submitReadRequest(pool, request) - this.logger.trace( - `Got revocation registry delta unparsed-response '${revocationRegistryDefinitionId}' from ledger`, - { - response, - } - ) - - const [, revocationRegistryDelta, deltaTimestamp] = await this.indy.parseGetRevocRegDeltaResponse(response) - - this.logger.debug(`Got revocation registry delta '${revocationRegistryDefinitionId}' from ledger`, { - revocationRegistryDelta, - deltaTimestamp, - to, - from, - }) - - return { revocationRegistryDelta, deltaTimestamp } - } catch (error) { - this.logger.error( - `Error retrieving revocation registry delta '${revocationRegistryDefinitionId}' from ledger, potentially revocation interval ends before revocation registry creation?"`, - { - error, - revocationRegistryId: revocationRegistryDefinitionId, - pool: pool.id, - } - ) - throw error - } - } - - // Retrieves the accumulated state of a revocation registry by id given a timestamp (used primarily for verification) - public async getRevocationRegistry( - agentContext: AgentContext, - revocationRegistryDefinitionId: string, - timestamp: number - ): Promise { - //TODO - implement a cache - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const { pool } = await this.indyPoolService.getPoolForDid(agentContext, did) - - this.logger.debug( - `Using ledger '${pool.id}' to retrieve revocation registry accumulated state with revocation registry definition id: '${revocationRegistryDefinitionId}'`, - { - timestamp, - } - ) - - try { - const request = await this.indy.buildGetRevocRegRequest(null, revocationRegistryDefinitionId, timestamp) - - this.logger.trace( - `Submitting get revocation registry request for revocation registry '${revocationRegistryDefinitionId}' to ledger` - ) - const response = await this.indyPoolService.submitReadRequest(pool, request) - this.logger.trace( - `Got un-parsed revocation registry '${revocationRegistryDefinitionId}' from ledger '${pool.id}'`, - { - response, - } - ) - - const [, revocationRegistry, ledgerTimestamp] = await this.indy.parseGetRevocRegResponse(response) - this.logger.debug(`Got revocation registry '${revocationRegistryDefinitionId}' from ledger`, { - ledgerTimestamp, - revocationRegistry, - }) - - return { revocationRegistry, ledgerTimestamp } - } catch (error) { - this.logger.error(`Error retrieving revocation registry '${revocationRegistryDefinitionId}' from ledger`, { - error, - revocationRegistryId: revocationRegistryDefinitionId, - pool: pool.id, - }) - throw error - } - } -} - -export interface SchemaTemplate { - name: string - version: string - attributes: string[] -} - -export interface CredentialDefinitionTemplate { - schema: Schema - tag: string - signatureType: 'CL' - supportRevocation: boolean -} - -export interface ParseRevocationRegistryDefinitionTemplate { - revocationRegistryDefinition: Indy.RevocRegDef - revocationRegistryDefinitionTxnTime: number -} - -export interface ParseRevocationRegistryDeltaTemplate { - revocationRegistryDelta: Indy.RevocRegDelta - deltaTimestamp: number -} - -export interface ParseRevocationRegistryTemplate { - revocationRegistry: Indy.RevocReg - ledgerTimestamp: number -} - -export interface IndyEndpointAttrib { - endpoint?: string - types?: Array<'endpoint' | 'did-communication' | 'DIDComm'> - routingKeys?: string[] - [key: string]: unknown -} diff --git a/packages/core/src/modules/ledger/services/IndyPoolService.ts b/packages/core/src/modules/ledger/services/IndyPoolService.ts deleted file mode 100644 index 172d1febd1..0000000000 --- a/packages/core/src/modules/ledger/services/IndyPoolService.ts +++ /dev/null @@ -1,349 +0,0 @@ -import type { AgentContext } from '../../../agent' -import type { AcceptanceMechanisms, AuthorAgreement, IndyPoolConfig } from '../IndyPool' -import type { default as Indy, LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' - -import { Subject } from 'rxjs' - -import { AgentDependencies } from '../../../agent/AgentDependencies' -import { InjectionSymbols } from '../../../constants' -import { IndySdkError } from '../../../error/IndySdkError' -import { Logger } from '../../../logger/Logger' -import { injectable, inject } from '../../../plugins' -import { FileSystem } from '../../../storage/FileSystem' -import { isSelfCertifiedDid } from '../../../utils/did' -import { isIndyError } from '../../../utils/indyError' -import { allSettled, onlyFulfilled, onlyRejected } from '../../../utils/promises' -import { assertIndyWallet } from '../../../wallet/util/assertIndyWallet' -import { CacheModuleConfig } from '../../cache' -import { IndyPool } from '../IndyPool' -import { LedgerError } from '../error/LedgerError' -import { LedgerNotConfiguredError } from '../error/LedgerNotConfiguredError' -import { LedgerNotFoundError } from '../error/LedgerNotFoundError' - -export interface CachedDidResponse { - nymResponse: Indy.GetNymResponse - poolId: string -} - -@injectable() -export class IndyPoolService { - public pools: IndyPool[] = [] - private logger: Logger - private indy: typeof Indy - private agentDependencies: AgentDependencies - private stop$: Subject - private fileSystem: FileSystem - - public constructor( - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger, - @inject(InjectionSymbols.Stop$) stop$: Subject, - @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem - ) { - this.logger = logger - this.indy = agentDependencies.indy - this.agentDependencies = agentDependencies - this.fileSystem = fileSystem - this.stop$ = stop$ - } - - public setPools(poolConfigs: IndyPoolConfig[]) { - this.pools = poolConfigs.map( - (poolConfig) => new IndyPool(poolConfig, this.agentDependencies, this.logger, this.stop$, this.fileSystem) - ) - } - - /** - * Create connections to all ledger pools - */ - public async connectToPools() { - const handleArray: number[] = [] - // Sequentially connect to pools so we don't use up too many resources connecting in parallel - for (const pool of this.pools) { - this.logger.debug(`Connecting to pool: ${pool.id}`) - const poolHandle = await pool.connect() - this.logger.debug(`Finished connection to pool: ${pool.id}`) - handleArray.push(poolHandle) - } - return handleArray - } - - /** - * @deprecated use instead getPoolForNamespace - * Get the pool used for writing to the ledger. For now we always use the first pool - * as the pool that writes to the ledger - */ - public get ledgerWritePool() { - if (this.pools.length === 0) { - throw new LedgerNotConfiguredError( - "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" - ) - } - - return this.pools[0] - } - - /** - * Get the most appropriate pool for the given did. The algorithm is based on the approach as described in this document: - * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit - */ - public async getPoolForDid( - agentContext: AgentContext, - did: string - ): Promise<{ pool: IndyPool; did: Indy.GetNymResponse }> { - const pools = this.pools - - if (pools.length === 0) { - throw new LedgerNotConfiguredError( - "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" - ) - } - - const cache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache - - const cachedNymResponse = await cache.get(agentContext, `IndySdkPoolService:${did}`) - const pool = this.pools.find((pool) => pool.id === cachedNymResponse?.poolId) - - // If we have the nym response with associated pool in the cache, we'll use that - if (cachedNymResponse && pool) { - this.logger.trace(`Found ledger id '${pool.id}' for did '${did}' in cache`) - return { did: cachedNymResponse.nymResponse, pool } - } - - const { successful, rejected } = await this.getSettledDidResponsesFromPools(did, pools) - - if (successful.length === 0) { - const allNotFound = rejected.every((e) => e.reason instanceof LedgerNotFoundError) - const rejectedOtherThanNotFound = rejected.filter((e) => !(e.reason instanceof LedgerNotFoundError)) - - // All ledgers returned response that the did was not found - if (allNotFound) { - throw new LedgerNotFoundError(`Did '${did}' not found on any of the ledgers (total ${this.pools.length}).`) - } - - // one or more of the ledgers returned an unknown error - throw new LedgerError( - `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers`, - { cause: rejectedOtherThanNotFound[0].reason } - ) - } - - // If there are self certified DIDs we always prefer it over non self certified DIDs - // We take the first self certifying DID as we take the order in the - // indyLedgers config as the order of preference of ledgers - let value = successful.find((response) => - isSelfCertifiedDid(response.value.did.did, response.value.did.verkey) - )?.value - - if (!value) { - // Split between production and nonProduction ledgers. If there is at least one - // successful response from a production ledger, only keep production ledgers - // otherwise we only keep the non production ledgers. - const production = successful.filter((s) => s.value.pool.config.isProduction) - const nonProduction = successful.filter((s) => !s.value.pool.config.isProduction) - const productionOrNonProduction = production.length >= 1 ? production : nonProduction - - // We take the first value as we take the order in the indyLedgers config as - // the order of preference of ledgers - value = productionOrNonProduction[0].value - } - - await cache.set(agentContext, `IndySdkPoolService:${did}`, { - nymResponse: value.did, - poolId: value.pool.id, - }) - return { pool: value.pool, did: value.did } - } - - private async getSettledDidResponsesFromPools(did: string, pools: IndyPool[]) { - this.logger.trace(`Retrieving did '${did}' from ${pools.length} ledgers`) - const didResponses = await allSettled(pools.map((pool) => this.getDidFromPool(did, pool))) - - const successful = onlyFulfilled(didResponses) - this.logger.trace(`Retrieved ${successful.length} responses from ledgers for did '${did}'`) - - const rejected = onlyRejected(didResponses) - - return { - rejected, - successful, - } - } - - /** - * Get the most appropriate pool for the given indyNamespace - */ - public getPoolForNamespace(indyNamespace?: string) { - if (this.pools.length === 0) { - throw new LedgerNotConfiguredError( - "No indy ledgers configured. Provide at least one pool configuration in the 'indyLedgers' agent configuration" - ) - } - - if (!indyNamespace) { - this.logger.warn('Not passing the indyNamespace is deprecated and will be removed in the future version.') - return this.pools[0] - } - - const pool = this.pools.find((pool) => pool.didIndyNamespace === indyNamespace) - - if (!pool) { - throw new LedgerNotFoundError(`No ledgers found for IndyNamespace '${indyNamespace}'.`) - } - - return pool - } - - public async submitWriteRequest( - agentContext: AgentContext, - pool: IndyPool, - request: LedgerRequest, - signDid: string - ): Promise { - try { - const requestWithTaa = await this.appendTaa(pool, request) - const signedRequestWithTaa = await this.signRequest(agentContext, signDid, requestWithTaa) - - const response = await pool.submitWriteRequest(signedRequestWithTaa) - - return response - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async submitReadRequest(pool: IndyPool, request: LedgerRequest): Promise { - try { - const response = await pool.submitReadRequest(request) - - return response - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async signRequest(agentContext: AgentContext, did: string, request: LedgerRequest): Promise { - assertIndyWallet(agentContext.wallet) - - try { - return this.indy.signRequest(agentContext.wallet.handle, did, request) - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async appendTaa(pool: IndyPool, request: Indy.LedgerRequest) { - try { - const authorAgreement = await this.getTransactionAuthorAgreement(pool) - const taa = pool.config.transactionAuthorAgreement - - // If ledger does not have TAA, we can just send request - if (authorAgreement == null) { - return request - } - // Ledger has taa but user has not specified which one to use - if (!taa) { - throw new LedgerError( - `Please, specify a transaction author agreement with version and acceptance mechanism. ${JSON.stringify( - authorAgreement - )}` - ) - } - - // Throw an error if the pool doesn't have the specified version and acceptance mechanism - if ( - authorAgreement.version !== taa.version || - !(taa.acceptanceMechanism in authorAgreement.acceptanceMechanisms.aml) - ) { - // Throw an error with a helpful message - const errMessage = `Unable to satisfy matching TAA with mechanism ${JSON.stringify( - taa.acceptanceMechanism - )} and version ${JSON.stringify(taa.version)} in pool.\n Found ${JSON.stringify( - Object.keys(authorAgreement.acceptanceMechanisms.aml) - )} and version ${authorAgreement.version} in pool.` - throw new LedgerError(errMessage) - } - - const requestWithTaa = await this.indy.appendTxnAuthorAgreementAcceptanceToRequest( - request, - authorAgreement.text, - taa.version, - authorAgreement.digest, - taa.acceptanceMechanism, - // Current time since epoch - // We can't use ratification_ts, as it must be greater than 1499906902 - Math.floor(new Date().getTime() / 1000) - ) - - return requestWithTaa - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async getTransactionAuthorAgreement(pool: IndyPool): Promise { - try { - // TODO Replace this condition with memoization - if (pool.authorAgreement !== undefined) { - return pool.authorAgreement - } - - const taaRequest = await this.indy.buildGetTxnAuthorAgreementRequest(null) - const taaResponse = await this.submitReadRequest(pool, taaRequest) - const acceptanceMechanismRequest = await this.indy.buildGetAcceptanceMechanismsRequest(null) - const acceptanceMechanismResponse = await this.submitReadRequest(pool, acceptanceMechanismRequest) - - // TAA can be null - if (taaResponse.result.data == null) { - pool.authorAgreement = null - return null - } - - // If TAA is not null, we can be sure AcceptanceMechanisms is also not null - const authorAgreement = taaResponse.result.data as AuthorAgreement - const acceptanceMechanisms = acceptanceMechanismResponse.result.data as AcceptanceMechanisms - pool.authorAgreement = { - ...authorAgreement, - acceptanceMechanisms, - } - return pool.authorAgreement - } catch (error) { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async getDidFromPool(did: string, pool: IndyPool): Promise { - try { - this.logger.trace(`Get public did '${did}' from ledger '${pool.id}'`) - const request = await this.indy.buildGetNymRequest(null, did) - - this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.id}'`) - const response = await pool.submitReadRequest(request) - - const result = await this.indy.parseGetNymResponse(response) - this.logger.trace(`Retrieved did '${did}' from ledger '${pool.id}'`, result) - - return { - did: result, - pool, - response, - } - } catch (error) { - this.logger.trace(`Error retrieving did '${did}' from ledger '${pool.id}'`, { - error, - did, - }) - if (isIndyError(error, 'LedgerNotFound')) { - throw new LedgerNotFoundError(`Did '${did}' not found on ledger ${pool.id}`) - } else { - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - } -} - -export interface PublicDidRequest { - did: Indy.GetNymResponse - pool: IndyPool - response: Indy.LedgerReadReplyResponse -} diff --git a/packages/core/src/modules/ledger/services/index.ts b/packages/core/src/modules/ledger/services/index.ts deleted file mode 100644 index e0399c9afe..0000000000 --- a/packages/core/src/modules/ledger/services/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './IndyLedgerService' -export * from './IndyPoolService' diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 675e7a3355..226462c2c1 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -7,14 +7,13 @@ import type { Constructor } from '../../utils/mixins' import { ProofsApi } from './ProofsApi' import { ProofsModuleConfig } from './ProofsModuleConfig' -import { IndyProofFormatService } from './formats/indy/IndyProofFormatService' import { V2ProofProtocol, V1ProofProtocol } from './protocol' import { ProofRepository } from './repository' /** * Default proofProtocols that will be registered if the `proofProtocols` property is not configured. */ -export type DefaultProofProtocols = [V1ProofProtocol, V2ProofProtocol] +export type DefaultProofProtocols = [V1ProofProtocol, V2ProofProtocol<[]>] // ProofsModuleOptions makes the proofProtocols property optional from the config, as it will set it when not provided. export type ProofsModuleOptions = Optional< @@ -32,26 +31,11 @@ export class ProofsModule } - /** - * Get the default proof protocols that will be registered if the `proofProtocols` property is not configured. - */ - private getDefaultProofProtocols(): DefaultProofProtocols { - // Instantiate proof formats - const indyProofFormat = new IndyProofFormatService() - - // Instantiate proof protocols - const v1ProofProtocol = new V1ProofProtocol({ indyProofFormat }) - const v2ProofProtocol = new V2ProofProtocol({ - proofFormats: [indyProofFormat], - }) - - return [v1ProofProtocol, v2ProofProtocol] - } - /** * Registers the dependencies of the proofs module on the dependency manager. */ diff --git a/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts b/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts deleted file mode 100644 index a81e4e5553..0000000000 --- a/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' - -export class InvalidEncodedValueError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts b/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts deleted file mode 100644 index a00abc40cb..0000000000 --- a/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' - -export class MissingIndyProofMessageError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/index.ts b/packages/core/src/modules/proofs/formats/index.ts index 08ece3aa21..a28e77d623 100644 --- a/packages/core/src/modules/proofs/formats/index.ts +++ b/packages/core/src/modules/proofs/formats/index.ts @@ -2,8 +2,6 @@ export * from './ProofFormat' export * from './ProofFormatService' export * from './ProofFormatServiceOptions' -export * from './indy' - import * as ProofFormatServiceOptions from './ProofFormatServiceOptions' export { ProofFormatServiceOptions } diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts deleted file mode 100644 index a6afce160a..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormat.ts +++ /dev/null @@ -1,80 +0,0 @@ -import type { ProofAttributeInfoOptions, ProofPredicateInfoOptions } from './models' -import type { RequestedAttributeOptions } from './models/RequestedAttribute' -import type { RequestedPredicateOptions } from './models/RequestedPredicate' -import type { V1PresentationPreviewAttributeOptions, V1PresentationPreviewPredicateOptions } from '../../protocol/v1' -import type { ProofFormat } from '../ProofFormat' -import type { IndyProof, IndyProofRequest } from 'indy-sdk' - -/** - * Interface for creating an indy proof proposal. - */ -export interface IndyProposeProofFormat { - name?: string - version?: string - attributes?: V1PresentationPreviewAttributeOptions[] - predicates?: V1PresentationPreviewPredicateOptions[] -} - -/** - * Interface for creating an indy proof request. - */ -export interface IndyRequestProofFormat { - name: string - version: string - // TODO: update to AnonCredsNonRevokedInterval when moving to AnonCreds package - nonRevoked?: { from?: number; to?: number } - requestedAttributes?: Record - requestedPredicates?: Record -} - -/** - * Interface for accepting an indy proof request. - */ -export type IndyAcceptProofRequestFormat = Partial - -export interface IndySelectedCredentialsForProofRequest { - requestedAttributes: Record - requestedPredicates: Record - selfAttestedAttributes: Record -} - -/** - * Interface for getting credentials for an indy proof request. - */ -export interface IndyCredentialsForProofRequest { - attributes: Record - predicates: Record -} - -export interface IndyGetCredentialsForProofRequestOptions { - filterByNonRevocationRequirements?: boolean -} - -export interface IndyProofFormat extends ProofFormat { - formatKey: 'indy' - - proofFormats: { - createProposal: IndyProposeProofFormat - acceptProposal: { - name?: string - version?: string - } - createRequest: IndyRequestProofFormat - acceptRequest: IndyAcceptProofRequestFormat - - getCredentialsForRequest: { - input: IndyGetCredentialsForProofRequestOptions - output: IndyCredentialsForProofRequest - } - selectCredentialsForRequest: { - input: IndyGetCredentialsForProofRequestOptions - output: IndySelectedCredentialsForProofRequest - } - } - - formatData: { - proposal: IndyProofRequest - request: IndyProofRequest - presentation: IndyProof - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts b/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts deleted file mode 100644 index 924f9dcb62..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/IndyProofFormatService.ts +++ /dev/null @@ -1,584 +0,0 @@ -import type { - IndyCredentialsForProofRequest, - IndyGetCredentialsForProofRequestOptions, - IndyProofFormat, - IndySelectedCredentialsForProofRequest, -} from './IndyProofFormat' -import type { ProofAttributeInfo, ProofPredicateInfo } from './models' -import type { AgentContext } from '../../../../agent' -import type { ProofFormatService } from '../ProofFormatService' -import type { - ProofFormatCreateProposalOptions, - ProofFormatCreateReturn, - ProofFormatAcceptProposalOptions, - ProofFormatAcceptRequestOptions, - ProofFormatAutoRespondProposalOptions, - ProofFormatAutoRespondRequestOptions, - ProofFormatGetCredentialsForRequestOptions, - ProofFormatGetCredentialsForRequestReturn, - ProofFormatSelectCredentialsForRequestOptions, - ProofFormatSelectCredentialsForRequestReturn, - ProofFormatProcessOptions, - FormatCreateRequestOptions, - ProofFormatProcessPresentationOptions, -} from '../ProofFormatServiceOptions' -import type { CredDef, IndyProof, IndyProofRequest, Schema } from 'indy-sdk' - -import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { JsonEncoder } from '../../../../utils/JsonEncoder' -import { JsonTransformer } from '../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { IndyCredential, IndyCredentialInfo } from '../../../credentials' -import { IndyCredentialUtils } from '../../../credentials/formats/indy/IndyCredentialUtils' -import { IndyVerifierService, IndyHolderService, IndyRevocationService } from '../../../indy' -import { IndyLedgerService } from '../../../ledger' -import { ProofFormatSpec } from '../../models/ProofFormatSpec' - -import { InvalidEncodedValueError } from './errors/InvalidEncodedValueError' -import { RequestedAttribute, RequestedPredicate } from './models' -import { PartialProof } from './models/PartialProof' -import { ProofRequest } from './models/ProofRequest' -import { RequestedCredentials } from './models/RequestedCredentials' -import { areIndyProofRequestsEqual, assertNoDuplicateGroupsNamesInProofRequest, createRequestFromPreview } from './util' -import { sortRequestedCredentials } from './util/sortRequestedCredentials' - -const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' -const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' -const V2_INDY_PRESENTATION = 'hlindy/proof@v2.0' - -export class IndyProofFormatService implements ProofFormatService { - public readonly formatKey = 'indy' as const - - public async createProposal( - agentContext: AgentContext, - { attachmentId, proofFormats }: ProofFormatCreateProposalOptions - ): Promise { - const format = new ProofFormatSpec({ - format: V2_INDY_PRESENTATION_PROPOSAL, - attachmentId, - }) - - const indyFormat = proofFormats.indy - if (!indyFormat) { - throw Error('Missing indy format to create proposal attachment format') - } - - const proofRequest = createRequestFromPreview({ - attributes: indyFormat.attributes ?? [], - predicates: indyFormat.predicates ?? [], - name: indyFormat.name ?? 'Proof request', - version: indyFormat.version ?? '1.0', - nonce: await agentContext.wallet.generateNonce(), - }) - const attachment = this.getFormatData(proofRequest.toJSON(), format.attachmentId) - - return { attachment, format } - } - - public async processProposal(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { - const proposalJson = attachment.getDataAsJson() - - // fromJSON also validates - const proposal = JsonTransformer.fromJSON(proposalJson, ProofRequest) - - // Assert attribute and predicate (group) names do not match - assertNoDuplicateGroupsNamesInProofRequest(proposal) - } - - public async acceptProposal( - agentContext: AgentContext, - { proposalAttachment, attachmentId }: ProofFormatAcceptProposalOptions - ): Promise { - const format = new ProofFormatSpec({ - format: V2_INDY_PRESENTATION_REQUEST, - attachmentId, - }) - - const proposalJson = proposalAttachment.getDataAsJson() - - // The proposal and request formats are the same, so we can just use the proposal - const request = JsonTransformer.fromJSON(proposalJson, ProofRequest) - - // We never want to reuse the nonce from the proposal, as this will allow replay attacks - request.nonce = await agentContext.wallet.generateNonce() - - const attachment = this.getFormatData(request.toJSON(), format.attachmentId) - - return { attachment, format } - } - - public async createRequest( - agentContext: AgentContext, - { attachmentId, proofFormats }: FormatCreateRequestOptions - ): Promise { - const format = new ProofFormatSpec({ - format: V2_INDY_PRESENTATION_REQUEST, - attachmentId, - }) - - const indyFormat = proofFormats.indy - if (!indyFormat) { - throw Error('Missing indy format in create request attachment format') - } - - const request = new ProofRequest({ - name: indyFormat.name, - version: indyFormat.version, - nonce: await agentContext.wallet.generateNonce(), - requestedAttributes: indyFormat.requestedAttributes, - requestedPredicates: indyFormat.requestedPredicates, - nonRevoked: indyFormat.nonRevoked, - }) - - // Validate to make sure user provided correct input - MessageValidator.validateSync(request) - assertNoDuplicateGroupsNamesInProofRequest(request) - - const attachment = this.getFormatData(request.toJSON(), format.attachmentId) - - return { attachment, format } - } - - public async processRequest(agentContext: AgentContext, { attachment }: ProofFormatProcessOptions): Promise { - const requestJson = attachment.getDataAsJson() - - // fromJSON also validates - const proofRequest = JsonTransformer.fromJSON(requestJson, ProofRequest) - - // Assert attribute and predicate (group) names do not match - assertNoDuplicateGroupsNamesInProofRequest(proofRequest) - } - - public async acceptRequest( - agentContext: AgentContext, - { proofFormats, requestAttachment, attachmentId }: ProofFormatAcceptRequestOptions - ): Promise { - const format = new ProofFormatSpec({ - format: V2_INDY_PRESENTATION, - attachmentId, - }) - - const indyFormat = proofFormats?.indy - - const requestJson = requestAttachment.getDataAsJson() - const proofRequest = JsonTransformer.fromJSON(requestJson, ProofRequest) - - let requestedCredentials: RequestedCredentials - - if (indyFormat) { - requestedCredentials = new RequestedCredentials({ - requestedAttributes: indyFormat.requestedAttributes, - requestedPredicates: indyFormat.requestedPredicates, - selfAttestedAttributes: indyFormat.selfAttestedAttributes, - }) - - // Validate to make sure user provided correct input - MessageValidator.validateSync(requestedCredentials) - } else { - const selectedCredentials = await this._selectCredentialsForRequest(agentContext, proofRequest, { - filterByNonRevocationRequirements: true, - }) - - requestedCredentials = new RequestedCredentials({ - requestedAttributes: selectedCredentials.requestedAttributes, - requestedPredicates: selectedCredentials.requestedPredicates, - selfAttestedAttributes: selectedCredentials.selfAttestedAttributes, - }) - } - - const proof = await this.createProof(agentContext, proofRequest, requestedCredentials) - const attachment = this.getFormatData(proof, format.attachmentId) - - return { - attachment, - format, - } - } - - public async processPresentation( - agentContext: AgentContext, - { requestAttachment, attachment }: ProofFormatProcessPresentationOptions - ): Promise { - const indyVerifierService = agentContext.dependencyManager.resolve(IndyVerifierService) - - const proofRequestJson = requestAttachment.getDataAsJson() - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - const proofJson = attachment.getDataAsJson() - const proof = JsonTransformer.fromJSON(proofJson, PartialProof) - - for (const [referent, attribute] of proof.requestedProof.revealedAttributes.entries()) { - if (!IndyCredentialUtils.checkValidEncoding(attribute.raw, attribute.encoded)) { - throw new InvalidEncodedValueError( - `The encoded value for '${referent}' is invalid. ` + - `Expected '${IndyCredentialUtils.encode(attribute.raw)}'. ` + - `Actual '${attribute.encoded}'` - ) - } - } - - // TODO: pre verify proof json - // I'm not 100% sure how much indy does. Also if it checks whether the proof requests matches the proof - // @see https://github.com/hyperledger/aries-cloudagent-python/blob/master/aries_cloudagent/indy/sdk/verifier.py#L79-L164 - - const schemas = await this.getSchemas(agentContext, new Set(proof.identifiers.map((i) => i.schemaId))) - const credentialDefinitions = await this.getCredentialDefinitions( - agentContext, - new Set(proof.identifiers.map((i) => i.credentialDefinitionId)) - ) - - return await indyVerifierService.verifyProof(agentContext, { - proofRequest: proofRequest.toJSON(), - proof: proofJson, - schemas, - credentialDefinitions, - }) - } - - public async getCredentialsForRequest( - agentContext: AgentContext, - { requestAttachment, proofFormats }: ProofFormatGetCredentialsForRequestOptions - ): Promise> { - const proofRequestJson = requestAttachment.getDataAsJson() - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - // Set default values - const { filterByNonRevocationRequirements = true } = proofFormats?.indy ?? {} - - const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequest, { - filterByNonRevocationRequirements, - }) - - return credentialsForRequest - } - - public async selectCredentialsForRequest( - agentContext: AgentContext, - { requestAttachment, proofFormats }: ProofFormatSelectCredentialsForRequestOptions - ): Promise> { - const proofRequestJson = requestAttachment.getDataAsJson() - const proofRequest = JsonTransformer.fromJSON(proofRequestJson, ProofRequest) - - // Set default values - const { filterByNonRevocationRequirements = true } = proofFormats?.indy ?? {} - - const selectedCredentials = this._selectCredentialsForRequest(agentContext, proofRequest, { - filterByNonRevocationRequirements, - }) - - return selectedCredentials - } - - public async shouldAutoRespondToProposal( - agentContext: AgentContext, - { proposalAttachment, requestAttachment }: ProofFormatAutoRespondProposalOptions - ): Promise { - const proposalJson = proposalAttachment.getDataAsJson() - const requestJson = requestAttachment.getDataAsJson() - - const areRequestsEqual = areIndyProofRequestsEqual(proposalJson, requestJson) - agentContext.config.logger.debug(`Indy request and proposal are are equal: ${areRequestsEqual}`, { - proposalJson, - requestJson, - }) - - return areRequestsEqual - } - - public async shouldAutoRespondToRequest( - agentContext: AgentContext, - { proposalAttachment, requestAttachment }: ProofFormatAutoRespondRequestOptions - ): Promise { - const proposalJson = proposalAttachment.getDataAsJson() - const requestJson = requestAttachment.getDataAsJson() - - return areIndyProofRequestsEqual(proposalJson, requestJson) - } - - public async shouldAutoRespondToPresentation(): Promise { - // The presentation is already verified in processPresentation, so we can just return true here. - // It's only an ack, so it's just that we received the presentation. - return true - } - - public supportsFormat(formatIdentifier: string): boolean { - const supportedFormats = [V2_INDY_PRESENTATION_PROPOSAL, V2_INDY_PRESENTATION_REQUEST, V2_INDY_PRESENTATION] - return supportedFormats.includes(formatIdentifier) - } - - private async _getCredentialsForRequest( - agentContext: AgentContext, - proofRequest: ProofRequest, - options: IndyGetCredentialsForProofRequestOptions - ): Promise { - const credentialsForProofRequest: IndyCredentialsForProofRequest = { - attributes: {}, - predicates: {}, - } - - const proofRequestJson = proofRequest.toJSON() - - for (const [referent, requestedAttribute] of proofRequest.requestedAttributes.entries()) { - const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequestJson, referent) - - credentialsForProofRequest.attributes[referent] = sortRequestedCredentials( - await Promise.all( - credentials.map(async (credential: IndyCredential) => { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { - proofRequest, - requestedItem: requestedAttribute, - credential, - }) - - return new RequestedAttribute({ - credentialId: credential.credentialInfo.referent, - revealed: true, - credentialInfo: credential.credentialInfo, - timestamp: deltaTimestamp, - revoked, - }) - }) - ) - ) - - // We only attach revoked state if non-revocation is requested. So if revoked is true it means - // the credential is not applicable to the proof request - if (options.filterByNonRevocationRequirements) { - credentialsForProofRequest.attributes[referent] = credentialsForProofRequest.attributes[referent].filter( - (r) => !r.revoked - ) - } - } - - for (const [referent, requestedPredicate] of proofRequest.requestedPredicates.entries()) { - const credentials = await this.getCredentialsForProofRequestReferent(agentContext, proofRequestJson, referent) - - credentialsForProofRequest.predicates[referent] = sortRequestedCredentials( - await Promise.all( - credentials.map(async (credential) => { - const { revoked, deltaTimestamp } = await this.getRevocationStatusForRequestedItem(agentContext, { - proofRequest, - requestedItem: requestedPredicate, - credential, - }) - - return new RequestedPredicate({ - credentialId: credential.credentialInfo.referent, - credentialInfo: credential.credentialInfo, - timestamp: deltaTimestamp, - revoked, - }) - }) - ) - ) - - // We only attach revoked state if non-revocation is requested. So if revoked is true it means - // the credential is not applicable to the proof request - if (options.filterByNonRevocationRequirements) { - credentialsForProofRequest.predicates[referent] = credentialsForProofRequest.predicates[referent].filter( - (r) => !r.revoked - ) - } - } - - return credentialsForProofRequest - } - - private async _selectCredentialsForRequest( - agentContext: AgentContext, - proofRequest: ProofRequest, - options: IndyGetCredentialsForProofRequestOptions - ): Promise { - const credentialsForRequest = await this._getCredentialsForRequest(agentContext, proofRequest, options) - - const selectedCredentials: IndySelectedCredentialsForProofRequest = { - requestedAttributes: {}, - requestedPredicates: {}, - selfAttestedAttributes: {}, - } - - Object.keys(credentialsForRequest.attributes).forEach((attributeName) => { - const attributeArray = credentialsForRequest.attributes[attributeName] - - if (attributeArray.length === 0) { - throw new AriesFrameworkError('Unable to automatically select requested attributes.') - } - - selectedCredentials.requestedAttributes[attributeName] = attributeArray[0] - }) - - Object.keys(credentialsForRequest.predicates).forEach((attributeName) => { - if (credentialsForRequest.predicates[attributeName].length === 0) { - throw new AriesFrameworkError('Unable to automatically select requested predicates.') - } else { - selectedCredentials.requestedPredicates[attributeName] = credentialsForRequest.predicates[attributeName][0] - } - }) - - return selectedCredentials - } - - private async getCredentialsForProofRequestReferent( - agentContext: AgentContext, - // pass as json to prevent having to transform to json on every call - proofRequestJson: IndyProofRequest, - attributeReferent: string - ): Promise { - const holderService = agentContext.dependencyManager.resolve(IndyHolderService) - - const credentialsJson = await holderService.getCredentialsForProofRequest(agentContext, { - proofRequest: proofRequestJson, - attributeReferent, - }) - - return JsonTransformer.fromJSON(credentialsJson, IndyCredential) as unknown as IndyCredential[] - } - - /** - * Build schemas object needed to create and verify proof objects. - * - * Creates object with `{ schemaId: Schema }` mapping - * - * @param schemaIds List of schema ids - * @returns Object containing schemas for specified schema ids - * - */ - private async getSchemas(agentContext: AgentContext, schemaIds: Set) { - const ledgerService = agentContext.dependencyManager.resolve(IndyLedgerService) - - const schemas: { [key: string]: Schema } = {} - - for (const schemaId of schemaIds) { - const schema = await ledgerService.getSchema(agentContext, schemaId) - schemas[schemaId] = schema - } - - return schemas - } - - /** - * Build credential definitions object needed to create and verify proof objects. - * - * Creates object with `{ credentialDefinitionId: CredentialDefinition }` mapping - * - * @param credentialDefinitionIds List of credential definition ids - * @returns Object containing credential definitions for specified credential definition ids - * - */ - private async getCredentialDefinitions(agentContext: AgentContext, credentialDefinitionIds: Set) { - const ledgerService = agentContext.dependencyManager.resolve(IndyLedgerService) - - const credentialDefinitions: { [key: string]: CredDef } = {} - - for (const credDefId of credentialDefinitionIds) { - const credDef = await ledgerService.getCredentialDefinition(agentContext, credDefId) - credentialDefinitions[credDefId] = credDef - } - - return credentialDefinitions - } - - /** - * Create indy proof from a given proof request and requested credential object. - * - * @param proofRequest The proof request to create the proof for - * @param requestedCredentials The requested credentials object specifying which credentials to use for the proof - * @returns indy proof object - */ - private async createProof( - agentContext: AgentContext, - proofRequest: ProofRequest, - requestedCredentials: RequestedCredentials - ): Promise { - const indyHolderService = agentContext.dependencyManager.resolve(IndyHolderService) - - const credentialObjects = await Promise.all( - [ - ...Object.values(requestedCredentials.requestedAttributes), - ...Object.values(requestedCredentials.requestedPredicates), - ].map(async (c) => { - if (c.credentialInfo) { - return c.credentialInfo - } - const credentialInfo = await indyHolderService.getCredential(agentContext, c.credentialId) - return JsonTransformer.fromJSON(credentialInfo, IndyCredentialInfo) - }) - ) - - const schemas = await this.getSchemas(agentContext, new Set(credentialObjects.map((c) => c.schemaId))) - const credentialDefinitions = await this.getCredentialDefinitions( - agentContext, - new Set(credentialObjects.map((c) => c.credentialDefinitionId)) - ) - - return await indyHolderService.createProof(agentContext, { - proofRequest: proofRequest.toJSON(), - requestedCredentials: requestedCredentials, - schemas, - credentialDefinitions, - }) - } - - private async getRevocationStatusForRequestedItem( - agentContext: AgentContext, - { - proofRequest, - requestedItem, - credential, - }: { - proofRequest: ProofRequest - requestedItem: ProofAttributeInfo | ProofPredicateInfo - credential: IndyCredential - } - ) { - const indyRevocationService = agentContext.dependencyManager.resolve(IndyRevocationService) - - const requestNonRevoked = requestedItem.nonRevoked ?? proofRequest.nonRevoked - const credentialRevocationId = credential.credentialInfo.credentialRevocationId - const revocationRegistryId = credential.credentialInfo.revocationRegistryId - - // If revocation interval is present and the credential is revocable then fetch the revocation status of credentials for display - if (requestNonRevoked && credentialRevocationId && revocationRegistryId) { - agentContext.config.logger.trace( - `Presentation is requesting proof of non revocation, getting revocation status for credential`, - { - requestNonRevoked, - credentialRevocationId, - revocationRegistryId, - } - ) - - // Note presentation from-to's vs ledger from-to's: https://github.com/hyperledger/indy-hipe/blob/master/text/0011-cred-revocation/README.md#indy-node-revocation-registry-intervals - const status = await indyRevocationService.getRevocationStatus( - agentContext, - credentialRevocationId, - revocationRegistryId, - requestNonRevoked - ) - - return status - } - - return { revoked: undefined, deltaTimestamp: undefined } - } - - /** - * Returns an object of type {@link Attachment} for use in credential exchange messages. - * It looks up the correct format identifier and encodes the data as a base64 attachment. - * - * @param data The data to include in the attach object - * @param id the attach id from the formats component of the message - */ - private getFormatData(data: unknown, id: string): Attachment { - const attachment = new Attachment({ - id, - mimeType: 'application/json', - data: new AttachmentData({ - base64: JsonEncoder.toBase64(data), - }), - }) - - return attachment - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/__tests__/groupKeys.ts b/packages/core/src/modules/proofs/formats/indy/__tests__/groupKeys.ts deleted file mode 100644 index 3d62914aca..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/__tests__/groupKeys.ts +++ /dev/null @@ -1,31 +0,0 @@ -import type { GetProofFormatDataReturn } from '../../../protocol/ProofProtocolOptions' -import type { IndyProofFormat } from '../IndyProofFormat' - -import { AriesFrameworkError } from '../../../../../error' - -export function getGroupKeysFromIndyProofFormatData(formatData: GetProofFormatDataReturn<[IndyProofFormat]>): { - proposeKey1: string - proposeKey2: string - requestKey1: string - requestKey2: string -} { - const proofRequest = formatData.request?.indy - const proofProposal = formatData.proposal?.indy - if (!proofProposal) { - throw new AriesFrameworkError('missing indy proof proposal') - } - if (!proofRequest) { - throw new AriesFrameworkError('missing indy proof request') - } - const proposeAttributes = proofProposal.requested_attributes - const proposePredicates = proofProposal.requested_predicates - const requestAttributes = proofRequest.requested_attributes - const requestPredicates = proofRequest.requested_predicates - - const proposeKey1 = Object.keys(proposeAttributes)[1] - const proposeKey2 = Object.keys(proposePredicates)[0] - const requestKey1 = Object.keys(requestAttributes)[1] - const requestKey2 = Object.keys(requestPredicates)[0] - - return { proposeKey1, proposeKey2, requestKey1, requestKey2 } -} diff --git a/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts b/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts deleted file mode 100644 index f4165bdb46..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts +++ /dev/null @@ -1,493 +0,0 @@ -import type { default as Indy } from 'indy-sdk' - -import { AriesFrameworkError } from '../../../../../error' -import { AttributeFilter, PredicateType, ProofAttributeInfo, ProofPredicateInfo, ProofRequest } from '../models' -import { areIndyProofRequestsEqual, assertNoDuplicateGroupsNamesInProofRequest } from '../util' - -const proofRequest = { - name: 'Proof Request', - version: '1.0.0', - nonce: 'nonce', - ver: '1.0', - non_revoked: {}, - requested_attributes: { - a: { - names: ['name1', 'name2'], - restrictions: [ - { - cred_def_id: 'cred_def_id1', - }, - { - schema_id: 'schema_id', - }, - ], - }, - }, - requested_predicates: { - p: { - name: 'Hello', - p_type: '<', - p_value: 10, - restrictions: [ - { - cred_def_id: 'string2', - }, - { - cred_def_id: 'string', - }, - ], - }, - }, -} satisfies Indy.IndyProofRequest - -const credDefId = '9vPXgSpQJPkJEALbLXueBp:3:CL:57753:tag1' -const nonce = 'testtesttest12345' - -describe('IndyProofFormat | util', () => { - describe('assertNoDuplicateGroupsNamesInProofRequest', () => { - test('attribute names match', () => { - const attributes = { - age1: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - age2: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const proofRequest = new ProofRequest({ - name: 'proof-request', - version: '1.0', - nonce, - requestedAttributes: attributes, - }) - - expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).not.toThrow() - }) - - test('attribute names match with predicates name', () => { - const attributes = { - attrib: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - predicate: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const proofRequest = new ProofRequest({ - name: 'proof-request', - version: '1.0', - nonce, - requestedAttributes: attributes, - requestedPredicates: predicates, - }) - - expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).toThrowError(AriesFrameworkError) - }) - }) - describe('areIndyProofRequestsEqual', () => { - test('does not compare name, ver, version and nonce', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - name: 'Proof Request 2', - version: '2.0.0', - nonce: 'nonce2', - ver: '2.0', - }) - ).toBe(true) - }) - - test('check top level non_revocation interval', () => { - // empty object is semantically equal to undefined - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - non_revoked: {}, - }) - ).toBe(true) - - // properties inside object are different - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - non_revoked: { - to: 5, - }, - }, - { - ...proofRequest, - non_revoked: { - from: 5, - }, - } - ) - ).toBe(false) - - // One has non_revoked, other doesn't - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - non_revoked: { - from: 5, - }, - }) - ).toBe(false) - }) - - test('ignores attribute group name differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - b: proofRequest.requested_attributes.a, - }, - }) - ).toBe(true) - }) - - test('ignores attribute restriction order', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - restrictions: [...proofRequest.requested_attributes.a.restrictions].reverse(), - }, - }, - }) - ).toBe(true) - }) - - test('ignores attribute restriction undefined vs empty array', () => { - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - restrictions: undefined, - }, - }, - }, - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - restrictions: [], - }, - }, - } - ) - ).toBe(true) - }) - - test('ignores attribute names order', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - names: ['name2', 'name1'], - }, - }, - }) - ).toBe(true) - }) - - test('checks attribute non_revocation interval', () => { - // empty object is semantically equal to undefined - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - non_revoked: {}, - }, - }, - }) - ).toBe(true) - - // properties inside object are different - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - non_revoked: { - to: 5, - }, - }, - }, - }, - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - non_revoked: { - from: 5, - }, - }, - }, - } - ) - ).toBe(false) - - // One has non_revoked, other doesn't - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - non_revoked: { - from: 5, - }, - }, - }, - }) - ).toBe(false) - }) - - test('checks attribute restriction differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - restrictions: [ - { - cred_def_id: 'cred_def_id1', - }, - { - cred_def_id: 'cred_def_id2', - }, - ], - }, - }, - }) - ).toBe(false) - }) - - test('checks attribute name differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - names: ['name3'], - }, - }, - }) - ).toBe(false) - - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - name: 'name3', - names: undefined, - }, - }, - }) - ).toBe(false) - }) - - test('ignores predicate group name differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - a: proofRequest.requested_predicates.p, - }, - }) - ).toBe(true) - }) - - test('ignores predicate restriction order', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - restrictions: [...proofRequest.requested_predicates.p.restrictions].reverse(), - }, - }, - }) - ).toBe(true) - }) - - test('ignores predicate restriction undefined vs empty array', () => { - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - restrictions: undefined, - }, - }, - }, - { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - restrictions: [], - }, - }, - } - ) - ).toBe(true) - }) - - test('checks predicate restriction differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - p: { - ...proofRequest.requested_predicates.p, - restrictions: [ - { - cred_def_id: 'cred_def_id1', - }, - { - cred_def_id: 'cred_def_id2', - }, - ], - }, - }, - }) - ).toBe(false) - }) - - test('checks predicate name differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - name: 'name3', - }, - }, - }) - ).toBe(false) - }) - - test('checks predicate non_revocation interval', () => { - // empty object is semantically equal to undefined - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - non_revoked: {}, - }, - }, - }) - ).toBe(true) - - // properties inside object are different - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - non_revoked: { - to: 5, - }, - }, - }, - }, - { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - non_revoked: { - from: 5, - }, - }, - }, - } - ) - ).toBe(false) - - // One has non_revoked, other doesn't - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - non_revoked: { - from: 5, - }, - }, - }, - }) - ).toBe(false) - }) - - test('checks predicate p_type and p_value', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - p_type: '<', - p_value: 134134, - }, - }, - }) - ).toBe(false) - }) - }) -}) diff --git a/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts b/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts deleted file mode 100644 index 84ac6e1385..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/errors/InvalidEncodedValueError.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' - -export class InvalidEncodedValueError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/indy/errors/index.ts b/packages/core/src/modules/proofs/formats/indy/errors/index.ts deleted file mode 100644 index 7b2373bb66..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/errors/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './InvalidEncodedValueError' diff --git a/packages/core/src/modules/proofs/formats/indy/index.ts b/packages/core/src/modules/proofs/formats/indy/index.ts deleted file mode 100644 index 185c2f8afc..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './IndyProofFormat' -export * from './IndyProofFormatService' diff --git a/packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts b/packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts deleted file mode 100644 index b2a804ab2d..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/AttributeFilter.ts +++ /dev/null @@ -1,145 +0,0 @@ -import { Expose, Transform, TransformationType, Type } from 'class-transformer' -import { IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' - -import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '../../../../../utils/regex' - -export class AttributeValue { - public constructor(options: AttributeValue) { - if (options) { - this.name = options.name - this.value = options.value - } - } - - @IsString() - public name!: string - - @IsString() - public value!: string -} - -export class AttributeFilter { - public constructor(options: AttributeFilter) { - if (options) { - this.schemaId = options.schemaId - this.schemaIssuerDid = options.schemaIssuerDid - this.schemaName = options.schemaName - this.schemaVersion = options.schemaVersion - this.issuerDid = options.issuerDid - this.credentialDefinitionId = options.credentialDefinitionId - this.attributeValue = options.attributeValue - } - } - - @Expose({ name: 'schema_id' }) - @IsOptional() - @IsString() - @Matches(schemaIdRegex) - public schemaId?: string - - @Expose({ name: 'schema_issuer_did' }) - @IsOptional() - @IsString() - @Matches(indyDidRegex) - public schemaIssuerDid?: string - - @Expose({ name: 'schema_name' }) - @IsOptional() - @IsString() - public schemaName?: string - - @Expose({ name: 'schema_version' }) - @IsOptional() - @IsString() - @Matches(schemaVersionRegex, { - message: 'Version must be X.X or X.X.X', - }) - public schemaVersion?: string - - @Expose({ name: 'issuer_did' }) - @IsOptional() - @IsString() - @Matches(indyDidRegex) - public issuerDid?: string - - @Expose({ name: 'cred_def_id' }) - @IsOptional() - @IsString() - @Matches(credDefIdRegex) - public credentialDefinitionId?: string - - @IsOptional() - @Type(() => AttributeValue) - @ValidateNested() - @IsInstance(AttributeValue) - public attributeValue?: AttributeValue -} - -/** - * Decorator that transforms attribute filter to corresponding class instances. - * Needed for transformation of attribute value filter. - * - * Transforms attribute value between these formats: - * - * JSON: - * ```json - * { - * "attr::test_prop::value": "test_value" - * } - * ``` - * - * Class: - * ```json - * { - * "attributeValue": { - * "name": "test_props", - * "value": "test_value" - * } - * } - * ``` - * - * @example - * class Example { - * AttributeFilterTransformer() - * public attributeFilter?: AttributeFilter; - * } - * - * @see https://github.com/hyperledger/aries-framework-dotnet/blob/a18bef91e5b9e4a1892818df7408e2383c642dfa/src/Hyperledger.Aries/Features/PresentProof/Models/AttributeFilterConverter.cs - */ -export function AttributeFilterTransformer() { - return Transform(({ value: attributeFilter, type: transformationType }) => { - switch (transformationType) { - case TransformationType.CLASS_TO_PLAIN: - if (attributeFilter.attributeValue) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - attributeFilter[`attr::${attributeFilter.attributeValue.name}::value`] = attributeFilter.attributeValue.value - delete attributeFilter.attributeValue - } - - return attributeFilter - - case TransformationType.PLAIN_TO_CLASS: - for (const [key, value] of Object.entries(attributeFilter)) { - const match = new RegExp('^attr::([^:]+)::(value)$').exec(key) - - if (match) { - const attributeValue = new AttributeValue({ - name: match[1], - value: value as string, - }) - - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - delete attributeFilter[key] - attributeFilter.attributeValue = attributeValue - - return attributeFilter - } - } - return attributeFilter - default: - return attributeFilter - } - }) -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/PartialProof.ts b/packages/core/src/modules/proofs/formats/indy/models/PartialProof.ts deleted file mode 100644 index c33627c99a..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/PartialProof.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Expose, Type } from 'class-transformer' -import { IsInstance, ValidateNested } from 'class-validator' - -import { ProofIdentifier } from './ProofIdentifier' -import { RequestedProof } from './RequestedProof' - -export class PartialProof { - public constructor(options: PartialProof) { - if (options) { - this.identifiers = options.identifiers - } - } - - @Type(() => ProofIdentifier) - @ValidateNested({ each: true }) - @IsInstance(ProofIdentifier, { each: true }) - public identifiers!: ProofIdentifier[] - - @Expose({ name: 'requested_proof' }) - @Type(() => RequestedProof) - @ValidateNested() - @IsInstance(RequestedProof) - public requestedProof!: RequestedProof -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts b/packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts deleted file mode 100644 index f5dda2fc14..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/PredicateType.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum PredicateType { - LessThan = '<', - LessThanOrEqualTo = '<=', - GreaterThan = '>', - GreaterThanOrEqualTo = '>=', -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofAttribute.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofAttribute.ts deleted file mode 100644 index f307f92da6..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofAttribute.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Expose } from 'class-transformer' -import { IsInt, IsString } from 'class-validator' - -export class ProofAttribute { - public constructor(options: ProofAttribute) { - if (options) { - this.subProofIndex = options.subProofIndex - this.raw = options.raw - this.encoded = options.encoded - } - } - - @Expose({ name: 'sub_proof_index' }) - @IsInt() - public subProofIndex!: number - - @IsString() - public raw!: string - - @IsString() - public encoded!: string -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts deleted file mode 100644 index a67c8425ae..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofAttributeInfo.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Expose, Type } from 'class-transformer' -import { ArrayNotEmpty, IsArray, IsInstance, IsOptional, IsString, ValidateIf, ValidateNested } from 'class-validator' - -import { IndyRevocationInterval } from '../../../../credentials' - -import { AttributeFilter } from './AttributeFilter' - -export type ProofAttributeInfoOptions = ProofAttributeInfo - -export class ProofAttributeInfo { - public constructor(options: ProofAttributeInfoOptions) { - if (options) { - this.name = options.name - this.names = options.names - this.nonRevoked = options.nonRevoked ? new IndyRevocationInterval(options.nonRevoked) : undefined - this.restrictions = options.restrictions?.map((r) => new AttributeFilter(r)) - } - } - - @IsString() - @ValidateIf((o: ProofAttributeInfo) => o.names === undefined) - public name?: string - - @IsArray() - @IsString({ each: true }) - @ValidateIf((o: ProofAttributeInfo) => o.name === undefined) - @ArrayNotEmpty() - public names?: string[] - - @Expose({ name: 'non_revoked' }) - @ValidateNested() - @IsInstance(IndyRevocationInterval) - @Type(() => IndyRevocationInterval) - @IsOptional() - public nonRevoked?: IndyRevocationInterval - - @ValidateNested({ each: true }) - @Type(() => AttributeFilter) - @IsOptional() - @IsInstance(AttributeFilter, { each: true }) - public restrictions?: AttributeFilter[] -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofIdentifier.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofIdentifier.ts deleted file mode 100644 index 241ac74aaa..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofIdentifier.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { Expose } from 'class-transformer' -import { IsNumber, IsOptional, IsString, Matches } from 'class-validator' - -import { credDefIdRegex } from '../../../../../utils/regex' - -export class ProofIdentifier { - public constructor(options: ProofIdentifier) { - if (options) { - this.schemaId = options.schemaId - this.credentialDefinitionId = options.credentialDefinitionId - this.revocationRegistryId = options.revocationRegistryId - this.timestamp = options.timestamp - } - } - - @Expose({ name: 'schema_id' }) - @IsString() - public schemaId!: string - - @Expose({ name: 'cred_def_id' }) - @IsString() - @Matches(credDefIdRegex) - public credentialDefinitionId!: string - - @Expose({ name: 'rev_reg_id' }) - @IsOptional() - @IsString() - public revocationRegistryId?: string - - @IsOptional() - @IsNumber() - public timestamp?: number -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts deleted file mode 100644 index 48083fc54d..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofPredicateInfo.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { Expose, Type } from 'class-transformer' -import { IsArray, IsEnum, IsInstance, IsInt, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { IndyRevocationInterval } from '../../../../credentials' - -import { AttributeFilter } from './AttributeFilter' -import { PredicateType } from './PredicateType' - -export interface ProofPredicateInfoOptions { - name: string - // Also allow string value of the enum as input, to make it easier to use in the API - predicateType: PredicateType | `${PredicateType}` - predicateValue: number - nonRevoked?: IndyRevocationInterval - restrictions?: AttributeFilter[] -} - -export class ProofPredicateInfo { - public constructor(options: ProofPredicateInfoOptions) { - if (options) { - this.name = options.name - this.nonRevoked = options.nonRevoked ? new IndyRevocationInterval(options.nonRevoked) : undefined - this.restrictions = options.restrictions?.map((r) => new AttributeFilter(r)) - this.predicateType = options.predicateType as PredicateType - this.predicateValue = options.predicateValue - } - } - - @IsString() - public name!: string - - @Expose({ name: 'p_type' }) - @IsEnum(PredicateType) - public predicateType!: PredicateType - - @Expose({ name: 'p_value' }) - @IsInt() - public predicateValue!: number - - @Expose({ name: 'non_revoked' }) - @ValidateNested() - @Type(() => IndyRevocationInterval) - @IsOptional() - @IsInstance(IndyRevocationInterval) - public nonRevoked?: IndyRevocationInterval - - @ValidateNested({ each: true }) - @Type(() => AttributeFilter) - @IsOptional() - @IsInstance(AttributeFilter, { each: true }) - @IsArray() - public restrictions?: AttributeFilter[] -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts b/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts deleted file mode 100644 index b2d5cf83cc..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/ProofRequest.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { ProofAttributeInfoOptions } from './ProofAttributeInfo' -import type { ProofPredicateInfoOptions } from './ProofPredicateInfo' -import type { IndyProofRequest } from 'indy-sdk' - -import { Expose, Type } from 'class-transformer' -import { IsIn, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IsMap } from '../../../../../utils/transformers' -import { IndyRevocationInterval } from '../../../../credentials' - -import { ProofAttributeInfo } from './ProofAttributeInfo' -import { ProofPredicateInfo } from './ProofPredicateInfo' - -export interface ProofRequestOptions { - name: string - version: string - nonce: string - nonRevoked?: IndyRevocationInterval - ver?: '1.0' | '2.0' - requestedAttributes?: Record - requestedPredicates?: Record -} - -/** - * Proof Request for Indy based proof format - * - * @see https://github.com/hyperledger/indy-sdk/blob/57dcdae74164d1c7aa06f2cccecaae121cefac25/libindy/src/api/anoncreds.rs#L1222-L1239 - */ -export class ProofRequest { - public constructor(options: ProofRequestOptions) { - if (options) { - this.name = options.name - this.version = options.version - this.nonce = options.nonce - - this.requestedAttributes = new Map( - Object.entries(options.requestedAttributes ?? {}).map(([key, attribute]) => [ - key, - new ProofAttributeInfo(attribute), - ]) - ) - - this.requestedPredicates = new Map( - Object.entries(options.requestedPredicates ?? {}).map(([key, predicate]) => [ - key, - new ProofPredicateInfo(predicate), - ]) - ) - - this.nonRevoked = options.nonRevoked ? new IndyRevocationInterval(options.nonRevoked) : undefined - this.ver = options.ver - } - } - - @IsString() - public name!: string - - @IsString() - public version!: string - - @IsString() - public nonce!: string - - @Expose({ name: 'requested_attributes' }) - @IsMap() - @ValidateNested({ each: true }) - @Type(() => ProofAttributeInfo) - @IsInstance(ProofAttributeInfo, { each: true }) - public requestedAttributes!: Map - - @Expose({ name: 'requested_predicates' }) - @IsMap() - @ValidateNested({ each: true }) - @Type(() => ProofPredicateInfo) - @IsInstance(ProofPredicateInfo, { each: true }) - public requestedPredicates!: Map - - @Expose({ name: 'non_revoked' }) - @ValidateNested() - @Type(() => IndyRevocationInterval) - @IsOptional() - @IsInstance(IndyRevocationInterval) - public nonRevoked?: IndyRevocationInterval - - @IsIn(['1.0', '2.0']) - @IsOptional() - public ver?: '1.0' | '2.0' - - public toJSON() { - // IndyProofRequest is indy-sdk json type - return JsonTransformer.toJSON(this) as unknown as IndyProofRequest - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts deleted file mode 100644 index 21a1e9a1c3..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedAttribute.ts +++ /dev/null @@ -1,47 +0,0 @@ -import type { IndyCredentialInfoOptions } from '../../../../credentials/formats/indy/models/IndyCredentialInfo' - -import { Exclude, Expose } from 'class-transformer' -import { IsBoolean, IsInt, IsOptional, IsString } from 'class-validator' - -import { IndyCredentialInfo } from '../../../../credentials/formats/indy/models/IndyCredentialInfo' - -export interface RequestedAttributeOptions { - credentialId: string - timestamp?: number - revealed: boolean - credentialInfo?: IndyCredentialInfoOptions - revoked?: boolean -} - -/** - * Requested Attribute for Indy proof creation - */ -export class RequestedAttribute { - public constructor(options: RequestedAttributeOptions) { - if (options) { - this.credentialId = options.credentialId - this.timestamp = options.timestamp - this.revealed = options.revealed - this.credentialInfo = options.credentialInfo ? new IndyCredentialInfo(options.credentialInfo) : undefined - this.revoked = options.revoked - } - } - - @Expose({ name: 'cred_id' }) - @IsString() - public credentialId!: string - - @Expose({ name: 'timestamp' }) - @IsInt() - @IsOptional() - public timestamp?: number - - @IsBoolean() - public revealed!: boolean - - @Exclude({ toPlainOnly: true }) - public credentialInfo?: IndyCredentialInfo - - @Exclude({ toPlainOnly: true }) - public revoked?: boolean -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts deleted file mode 100644 index f515a82dee..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedCredentials.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { RequestedAttributeOptions } from './RequestedAttribute' -import type { RequestedPredicateOptions } from './RequestedPredicate' -import type { IndyRequestedCredentials } from 'indy-sdk' - -import { Expose } from 'class-transformer' -import { ValidateNested } from 'class-validator' - -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { RecordTransformer } from '../../../../../utils/transformers' - -import { RequestedAttribute } from './RequestedAttribute' -import { RequestedPredicate } from './RequestedPredicate' - -export interface IndyRequestedCredentialsOptions { - requestedAttributes?: Record - requestedPredicates?: Record - selfAttestedAttributes?: Record -} - -/** - * Requested Credentials for Indy proof creation - * - * @see https://github.com/hyperledger/indy-sdk/blob/57dcdae74164d1c7aa06f2cccecaae121cefac25/libindy/src/api/anoncreds.rs#L1433-L1445 - */ -export class RequestedCredentials { - public constructor(options: IndyRequestedCredentialsOptions = {}) { - if (options) { - const { requestedAttributes, requestedPredicates } = options - - // Create RequestedAttribute objects from options - this.requestedAttributes = {} - if (requestedAttributes) { - Object.keys(requestedAttributes).forEach((key) => { - this.requestedAttributes[key] = new RequestedAttribute(requestedAttributes[key]) - }) - } - - // Create RequestedPredicate objects from options - this.requestedPredicates = {} - if (requestedPredicates) { - Object.keys(requestedPredicates).forEach((key) => { - this.requestedPredicates[key] = new RequestedPredicate(requestedPredicates[key]) - }) - } - - this.selfAttestedAttributes = options.selfAttestedAttributes ?? {} - } - } - - @Expose({ name: 'requested_attributes' }) - @ValidateNested({ each: true }) - @RecordTransformer(RequestedAttribute) - public requestedAttributes!: Record - - @Expose({ name: 'requested_predicates' }) - @ValidateNested({ each: true }) - @RecordTransformer(RequestedPredicate) - public requestedPredicates!: Record - - @Expose({ name: 'self_attested_attributes' }) - public selfAttestedAttributes!: Record - - public toJSON() { - // IndyRequestedCredentials is indy-sdk json type - return JsonTransformer.toJSON(this) as unknown as IndyRequestedCredentials - } - - public getCredentialIdentifiers(): string[] { - const credIds = new Set() - - Object.values(this.requestedAttributes).forEach((attr) => { - credIds.add(attr.credentialId) - }) - - Object.values(this.requestedPredicates).forEach((pred) => { - credIds.add(pred.credentialId) - }) - - return Array.from(credIds) - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts deleted file mode 100644 index d8f5e2d9d2..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedPredicate.ts +++ /dev/null @@ -1,42 +0,0 @@ -import type { IndyCredentialInfoOptions } from '../../../../credentials' - -import { Exclude, Expose } from 'class-transformer' -import { IsInt, IsOptional, IsString } from 'class-validator' - -import { IndyCredentialInfo } from '../../../../credentials' - -export interface RequestedPredicateOptions { - credentialId: string - timestamp?: number - credentialInfo?: IndyCredentialInfoOptions - revoked?: boolean -} - -/** - * Requested Predicate for Indy proof creation - */ -export class RequestedPredicate { - public constructor(options: RequestedPredicateOptions) { - if (options) { - this.credentialId = options.credentialId - this.timestamp = options.timestamp - this.credentialInfo = options.credentialInfo ? new IndyCredentialInfo(options.credentialInfo) : undefined - this.revoked = options.revoked - } - } - - @Expose({ name: 'cred_id' }) - @IsString() - public credentialId!: string - - @Expose({ name: 'timestamp' }) - @IsInt() - @IsOptional() - public timestamp?: number - - @Exclude({ toPlainOnly: true }) - public credentialInfo?: IndyCredentialInfo - - @Exclude({ toPlainOnly: true }) - public revoked?: boolean -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RequestedProof.ts b/packages/core/src/modules/proofs/formats/indy/models/RequestedProof.ts deleted file mode 100644 index a2f2a5cf85..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RequestedProof.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { Expose, Type } from 'class-transformer' -import { IsInstance, IsOptional, ValidateNested } from 'class-validator' - -import { ProofAttribute } from './ProofAttribute' - -export class RequestedProof { - public constructor(options: RequestedProof) { - if (options) { - this.revealedAttributes = options.revealedAttributes - this.selfAttestedAttributes = options.selfAttestedAttributes - } - } - - @Expose({ name: 'revealed_attrs' }) - @ValidateNested({ each: true }) - @Type(() => ProofAttribute) - @IsInstance(ProofAttribute, { each: true }) - public revealedAttributes!: Map - - @Expose({ name: 'self_attested_attrs' }) - @IsOptional() - // Validation is relaxed/skipped because empty Map validation will fail on JSON transform validation - public selfAttestedAttributes: Map = new Map() -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts b/packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts deleted file mode 100644 index e529b24065..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/RetrievedCredentials.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { RequestedAttribute } from './RequestedAttribute' -import type { RequestedPredicate } from './RequestedPredicate' - -export interface RetrievedCredentialsOptions { - requestedAttributes?: Record - requestedPredicates?: Record -} - -/** - * Lists of requested credentials for Indy proof creation - */ -export class RetrievedCredentials { - public requestedAttributes: Record - public requestedPredicates: Record - - public constructor(options: RetrievedCredentialsOptions = {}) { - this.requestedAttributes = options.requestedAttributes ?? {} - this.requestedPredicates = options.requestedPredicates ?? {} - } -} diff --git a/packages/core/src/modules/proofs/formats/indy/models/__tests__/ProofRequest.test.ts b/packages/core/src/modules/proofs/formats/indy/models/__tests__/ProofRequest.test.ts deleted file mode 100644 index 9d52625ece..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/__tests__/ProofRequest.test.ts +++ /dev/null @@ -1,79 +0,0 @@ -import { ClassValidationError } from '../../../../../../error/ClassValidationError' -import { JsonTransformer } from '../../../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../../../utils/MessageValidator' -import { ProofRequest } from '../ProofRequest' - -describe('ProofRequest', () => { - it('should successfully validate if the proof request JSON contains a valid structure', async () => { - const proofRequest = JsonTransformer.fromJSON( - { - name: 'ProofRequest', - version: '1.0', - nonce: '947121108704767252195123', - requested_attributes: { - First: { - name: 'Timo', - restrictions: [ - { - schema_id: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - }, - ], - }, - }, - requested_predicates: { - Second: { - name: 'Timo', - p_type: '<=', - p_value: 10, - restrictions: [ - { - schema_id: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - }, - ], - }, - }, - }, - ProofRequest - ) - - expect(() => MessageValidator.validateSync(proofRequest)).not.toThrow() - }) - - it('should throw an error if the proof request json contains an invalid structure', async () => { - const proofRequest = { - name: 'ProofRequest', - version: '1.0', - nonce: '947121108704767252195123', - requested_attributes: { - First: { - names: [], - restrictions: [ - { - schema_id: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - }, - ], - }, - }, - requested_predicates: [ - { - name: 'Timo', - p_type: '<=', - p_value: 10, - restrictions: [ - { - schema_id: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - }, - ], - }, - ], - } - - expect(() => JsonTransformer.fromJSON(proofRequest, ProofRequest)).toThrowError(ClassValidationError) - try { - JsonTransformer.fromJSON(proofRequest, ProofRequest) - } catch (e) { - const caughtError = e as ClassValidationError - expect(caughtError.validationErrors).toHaveLength(2) - } - }) -}) diff --git a/packages/core/src/modules/proofs/formats/indy/models/index.ts b/packages/core/src/modules/proofs/formats/indy/models/index.ts deleted file mode 100644 index 978b3ee89f..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/models/index.ts +++ /dev/null @@ -1,9 +0,0 @@ -export * from './AttributeFilter' -export * from './PredicateType' -export * from './ProofAttributeInfo' -export * from './ProofPredicateInfo' -export * from './ProofRequest' -export * from './RequestedAttribute' -export * from './RequestedCredentials' -export * from './RequestedPredicate' -export * from './RetrievedCredentials' diff --git a/packages/core/src/modules/proofs/formats/indy/util.ts b/packages/core/src/modules/proofs/formats/indy/util.ts deleted file mode 100644 index b68e50a6b4..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/util.ts +++ /dev/null @@ -1,252 +0,0 @@ -import type { V1PresentationPreviewAttributeOptions, V1PresentationPreviewPredicateOptions } from '../../protocol' -import type { default as Indy } from 'indy-sdk' - -import { AriesFrameworkError } from '../../../../error' -import { areObjectsEqual } from '../../../../utils' -import { uuid } from '../../../../utils/uuid' - -import { ProofAttributeInfo, ProofPredicateInfo, ProofRequest } from './models' - -export function createRequestFromPreview({ - name, - version, - nonce, - attributes, - predicates, -}: { - name: string - version: string - nonce: string - attributes: V1PresentationPreviewAttributeOptions[] - predicates: V1PresentationPreviewPredicateOptions[] -}): ProofRequest { - const proofRequest = new ProofRequest({ - name, - version, - nonce, - }) - - /** - * Create mapping of attributes by referent. This required the - * attributes to come from the same credential. - * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent - * - * { - * "referent1": [Attribute1, Attribute2], - * "referent2": [Attribute3] - * } - */ - const attributesByReferent: Record = {} - for (const proposedAttributes of attributes ?? []) { - if (!proposedAttributes.referent) proposedAttributes.referent = uuid() - - const referentAttributes = attributesByReferent[proposedAttributes.referent] - - // Referent key already exist, add to list - if (referentAttributes) { - referentAttributes.push(proposedAttributes) - } - - // Referent key does not exist yet, create new entry - else { - attributesByReferent[proposedAttributes.referent] = [proposedAttributes] - } - } - - // Transform attributes by referent to requested attributes - for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { - // Either attributeName or attributeNames will be undefined - const attributeName = proposedAttributes.length == 1 ? proposedAttributes[0].name : undefined - const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined - - const requestedAttribute = new ProofAttributeInfo({ - name: attributeName, - names: attributeNames, - restrictions: [ - { - credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, - }, - ], - }) - - proofRequest.requestedAttributes.set(referent, requestedAttribute) - } - - // Transform proposed predicates to requested predicates - for (const proposedPredicate of predicates ?? []) { - const requestedPredicate = new ProofPredicateInfo({ - name: proposedPredicate.name, - predicateType: proposedPredicate.predicate, - predicateValue: proposedPredicate.threshold, - restrictions: [ - { - credentialDefinitionId: proposedPredicate.credentialDefinitionId, - }, - ], - }) - - proofRequest.requestedPredicates.set(uuid(), requestedPredicate) - } - - return proofRequest -} - -/** - * Checks whether two `names` arrays are equal. The order of the names doesn't matter. - */ -function areNamesEqual(namesA: string[] | undefined, namesB: string[] | undefined) { - if (namesA === undefined) return namesB === undefined || namesB.length === 0 - if (namesB === undefined) return namesA.length === 0 - - // Check if there are any duplicates - if (new Set(namesA).size !== namesA.length || new Set(namesB).size !== namesB.length) return false - - // Check if the number of names is equal between A & B - if (namesA.length !== namesB.length) return false - - return namesA.every((a) => namesB.includes(a)) -} - -/** - * Checks whether two proof requests are semantically equal. The `name`, `version` and `nonce`, `ver` fields are ignored. - * In addition the group names don't have to be the same between the different requests. - */ -export function areIndyProofRequestsEqual(requestA: Indy.IndyProofRequest, requestB: Indy.IndyProofRequest): boolean { - // Check if the top-level non-revocation interval is equal - if (!isNonRevokedEqual(requestA.non_revoked, requestB.non_revoked)) return false - - const attributeAList = Object.values(requestA.requested_attributes) - const attributeBList = Object.values(requestB.requested_attributes) - - // Check if the number of attribute groups is equal in both requests - if (attributeAList.length !== attributeBList.length) return false - - // Check if all attribute groups in A are also in B - const attributesMatch = attributeAList.every((a) => { - // find an attribute in B that matches this attribute - const bIndex = attributeBList.findIndex((b) => { - return ( - b.name === a.name && - areNamesEqual(a.names, b.names) && - isNonRevokedEqual(a.non_revoked, b.non_revoked) && - areRestrictionsEqual(a.restrictions, b.restrictions) - ) - }) - - // Match found - if (bIndex !== -1) { - attributeBList.splice(bIndex, 1) - return true - } - - // Match not found - return false - }) - - if (!attributesMatch) return false - - const predicatesA = Object.values(requestA.requested_predicates) - const predicatesB = Object.values(requestB.requested_predicates) - - if (predicatesA.length !== predicatesB.length) return false - const predicatesMatch = predicatesA.every((a) => { - // find a predicate in B that matches this predicate - const bIndex = predicatesB.findIndex((b) => { - return ( - a.name === b.name && - a.p_type === b.p_type && - a.p_value === b.p_value && - isNonRevokedEqual(a.non_revoked, b.non_revoked) && - areRestrictionsEqual(a.restrictions, b.restrictions) - ) - }) - - if (bIndex !== -1) { - predicatesB.splice(bIndex, 1) - return true - } - - return false - }) - - if (!predicatesMatch) return false - - return true -} - -/** - * Checks whether two non-revocation intervals are semantically equal. They are considered equal if: - * - Both are undefined - * - Both are empty objects - * - One if undefined and the other is an empty object - * - Both have the same from and to values - */ -function isNonRevokedEqual( - nonRevokedA: Indy.NonRevokedInterval | undefined, - nonRevokedB: Indy.NonRevokedInterval | undefined -) { - // Having an empty non-revoked object is the same as not having one - if (nonRevokedA === undefined) - return nonRevokedB === undefined || (nonRevokedB.from === undefined && nonRevokedB.to === undefined) - if (nonRevokedB === undefined) return nonRevokedA.from === undefined && nonRevokedA.to === undefined - - return nonRevokedA.from === nonRevokedB.from && nonRevokedA.to === nonRevokedB.to -} - -/** - * Check if two restriction lists are equal. The order of the restrictions does not matter. - */ -function areRestrictionsEqual( - restrictionsA: Indy.WalletQuery[] | undefined, - restrictionsB: Indy.WalletQuery[] | undefined -) { - // Having an undefined restrictions property or an empty array is the same - if (restrictionsA === undefined) return restrictionsB === undefined || restrictionsB.length === 0 - if (restrictionsB === undefined) return restrictionsA.length === 0 - - // Clone array to not modify input object - const bList = [...restrictionsB] - - // Check if all restrictions in A are also in B - return restrictionsA.every((a) => { - const bIndex = restrictionsB.findIndex((b) => areObjectsEqual(a, b)) - - // Match found - if (bIndex !== -1) { - bList.splice(bIndex, 1) - return true - } - - // Match not found - return false - }) -} - -function attributeNamesToArray(proofRequest: ProofRequest) { - // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array - // containing all attribute names from the requested attributes. - return Array.from(proofRequest.requestedAttributes.values()).reduce( - (names, a) => [...names, ...(a.name ? [a.name] : a.names ? a.names : [])], - [] - ) -} - -function predicateNamesToArray(proofRequest: ProofRequest) { - return Array.from(new Set(Array.from(proofRequest.requestedPredicates.values()).map((a) => a.name))) -} - -function assertNoDuplicates(predicates: string[], attributeNames: string[]) { - const duplicates = predicates.filter((item) => attributeNames.indexOf(item) !== -1) - if (duplicates.length > 0) { - throw new AriesFrameworkError( - `The proof request contains duplicate predicates and attributes: ${duplicates.toString()}` - ) - } -} - -// TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. -export function assertNoDuplicateGroupsNamesInProofRequest(proofRequest: ProofRequest) { - const attributes = attributeNamesToArray(proofRequest) - const predicates = predicateNamesToArray(proofRequest) - assertNoDuplicates(predicates, attributes) -} diff --git a/packages/core/src/modules/proofs/formats/indy/util/__tests__/sortRequestedCredentials.test.ts b/packages/core/src/modules/proofs/formats/indy/util/__tests__/sortRequestedCredentials.test.ts deleted file mode 100644 index 117fe2b898..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/util/__tests__/sortRequestedCredentials.test.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { RequestedAttribute } from '../../models' -import { sortRequestedCredentials } from '../sortRequestedCredentials' - -const credentials = [ - new RequestedAttribute({ - credentialId: '1', - revealed: true, - revoked: true, - }), - new RequestedAttribute({ - credentialId: '2', - revealed: true, - revoked: undefined, - }), - new RequestedAttribute({ - credentialId: '3', - revealed: true, - revoked: false, - }), - new RequestedAttribute({ - credentialId: '4', - revealed: true, - revoked: false, - }), - new RequestedAttribute({ - credentialId: '5', - revealed: true, - revoked: true, - }), - new RequestedAttribute({ - credentialId: '6', - revealed: true, - revoked: undefined, - }), -] - -describe('sortRequestedCredentials', () => { - test('sorts the credentials', () => { - expect(sortRequestedCredentials(credentials)).toEqual([ - credentials[1], - credentials[5], - credentials[2], - credentials[3], - credentials[0], - credentials[4], - ]) - }) -}) diff --git a/packages/core/src/modules/proofs/formats/indy/util/sortRequestedCredentials.ts b/packages/core/src/modules/proofs/formats/indy/util/sortRequestedCredentials.ts deleted file mode 100644 index 2db1deb0b9..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/util/sortRequestedCredentials.ts +++ /dev/null @@ -1,33 +0,0 @@ -import type { RequestedAttribute, RequestedPredicate } from '../models' - -/** - * Sort requested attributes and predicates by `revoked` status. The order is: - * - first credentials with `revoked` set to undefined, this means no revocation status is needed for the credentials - * - then credentials with `revoked` set to false, this means the credentials are not revoked - * - then credentials with `revoked` set to true, this means the credentials are revoked - */ -export function sortRequestedCredentials | Array>( - credentials: Requested -) { - const staySame = 0 - const credentialGoUp = -1 - const credentialGoDown = 1 - - // Clone as sort is in place - const credentialsClone = [...credentials] - - return credentialsClone.sort((credential, compareTo) => { - // Nothing needs to happen if values are the same - if (credential.revoked === compareTo.revoked) return staySame - - // Undefined always is at the top - if (credential.revoked === undefined) return credentialGoUp - if (compareTo.revoked === undefined) return credentialGoDown - - // Then revoked - if (credential.revoked === false) return credentialGoUp - - // It means that compareTo is false and credential is true - return credentialGoDown - }) -} diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts index fcfaaaebf1..885701ec96 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -9,9 +9,9 @@ import { setupProofsTest, waitForProofExchangeRecordSubject, getAgentOptions, - prepareForIssuance, + prepareForIndyIssuance, makeConnection, - issueCredential, + issueLegacyAnonCredsCredential, } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' @@ -265,13 +265,13 @@ describe('Present Proof', () => { agents = [aliceAgent, faberAgent, mediatorAgent] - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) + const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) expect(faberConnection.isReady).toBe(true) expect(aliceConnection.isReady).toBe(true) - await issueCredential({ + await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, issuerConnectionId: faberConnection.id, holderAgent: aliceAgent, diff --git a/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts b/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts index efc6a02aad..54615d7034 100644 --- a/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts +++ b/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts @@ -2,7 +2,6 @@ import type { AgentContext } from '../../../../agent' import type { Attachment } from '../../../../decorators/attachment/Attachment' import type { ExtractProofFormats, - IndyProofFormat, ProofFormatCredentialForRequestPayload, ProofFormatPayload, ProofFormatService, diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts index f924869f3f..fbec2351df 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -9,9 +9,9 @@ import { setupProofsTest, waitForProofExchangeRecordSubject, getAgentOptions, - prepareForIssuance, + prepareForIndyIssuance, makeConnection, - issueCredential, + issueLegacyAnonCredsCredential, } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' @@ -266,14 +266,14 @@ describe('Present Proof', () => { agents = [aliceAgent, faberAgent, mediatorAgent] - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) + const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) expect(faberConnection.isReady).toBe(true) expect(aliceConnection.isReady).toBe(true) // issue credential with two linked attachments - await issueCredential({ + await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, issuerConnectionId: faberConnection.id, holderAgent: aliceAgent, diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index 7c9bfab59f..f3a9bdb441 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -13,18 +13,14 @@ import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' -const recipientAgentOptions = getAgentOptions('Mediation: Recipient', { - indyLedgers: [], -}) +const recipientAgentOptions = getAgentOptions('Mediation: Recipient') const mediatorAgentOptions = getAgentOptions('Mediation: Mediator', { autoAcceptMediationRequests: true, endpoints: ['rxjs:mediator'], - indyLedgers: [], }) const senderAgentOptions = getAgentOptions('Mediation: Sender', { endpoints: ['rxjs:sender'], - indyLedgers: [], }) describe('mediator establishment', () => { diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts b/packages/core/src/modules/routing/__tests__/pickup.test.ts index 8b33fcacf2..65587fee96 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ b/packages/core/src/modules/routing/__tests__/pickup.test.ts @@ -12,12 +12,10 @@ import { MediatorPickupStrategy } from '../MediatorPickupStrategy' const recipientOptions = getAgentOptions('Mediation: Recipient Pickup', { autoAcceptConnections: true, - indyLedgers: [], }) const mediatorOptions = getAgentOptions('Mediation: Mediator Pickup', { autoAcceptConnections: true, endpoints: ['wss://mediator'], - indyLedgers: [], }) describe('E2E Pick Up protocol', () => { diff --git a/packages/core/src/plugins/Module.ts b/packages/core/src/plugins/Module.ts index 93196d71cf..bca1bc5ff7 100644 --- a/packages/core/src/plugins/Module.ts +++ b/packages/core/src/plugins/Module.ts @@ -1,10 +1,12 @@ import type { DependencyManager } from './DependencyManager' +import type { AgentContext } from '../agent' import type { FeatureRegistry } from '../agent/FeatureRegistry' import type { Constructor } from '../utils/mixins' export interface Module { api?: Constructor register(dependencyManager: DependencyManager, featureRegistry: FeatureRegistry): void + initialize?(agentContext: AgentContext): Promise } export interface ApiModule extends Module { diff --git a/packages/core/src/storage/IndyStorageService.ts b/packages/core/src/storage/IndyStorageService.ts deleted file mode 100644 index 452ef555c1..0000000000 --- a/packages/core/src/storage/IndyStorageService.ts +++ /dev/null @@ -1,323 +0,0 @@ -import type { BaseRecord, TagsBase } from './BaseRecord' -import type { BaseRecordConstructor, Query, StorageService } from './StorageService' -import type { AgentContext } from '../agent' -import type { IndyWallet } from '../wallet/IndyWallet' -import type { default as Indy, WalletQuery, WalletRecord, WalletSearchOptions } from 'indy-sdk' - -import { AgentDependencies } from '../agent/AgentDependencies' -import { InjectionSymbols } from '../constants' -import { IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' -import { injectable, inject } from '../plugins' -import { JsonTransformer } from '../utils/JsonTransformer' -import { isIndyError } from '../utils/indyError' -import { isBoolean } from '../utils/type' -import { assertIndyWallet } from '../wallet/util/assertIndyWallet' - -@injectable() -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export class IndyStorageService> implements StorageService { - private indy: typeof Indy - - private static DEFAULT_QUERY_OPTIONS = { - retrieveType: true, - retrieveTags: true, - } - - public constructor(@inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies) { - this.indy = agentDependencies.indy - } - - private transformToRecordTagValues(tags: { [key: number]: string | undefined }): TagsBase { - const transformedTags: TagsBase = {} - - for (const [key, value] of Object.entries(tags)) { - // If the value is a boolean string ('1' or '0') - // use the boolean val - if (value === '1' && key?.includes(':')) { - const [tagName, tagValue] = key.split(':') - - const transformedValue = transformedTags[tagName] - - if (Array.isArray(transformedValue)) { - transformedTags[tagName] = [...transformedValue, tagValue] - } else { - transformedTags[tagName] = [tagValue] - } - } - // Transform '1' and '0' to boolean - else if (value === '1' || value === '0') { - transformedTags[key] = value === '1' - } - // If 1 or 0 is prefixed with 'n__' we need to remove it. This is to prevent - // casting the value to a boolean - else if (value === 'n__1' || value === 'n__0') { - transformedTags[key] = value === 'n__1' ? '1' : '0' - } - // Otherwise just use the value - else { - transformedTags[key] = value - } - } - - return transformedTags - } - - private transformFromRecordTagValues(tags: TagsBase): { [key: string]: string | undefined } { - const transformedTags: { [key: string]: string | undefined } = {} - - for (const [key, value] of Object.entries(tags)) { - // If the value is of type null we use the value undefined - // Indy doesn't support null as a value - if (value === null) { - transformedTags[key] = undefined - } - // If the value is a boolean use the indy - // '1' or '0' syntax - else if (isBoolean(value)) { - transformedTags[key] = value ? '1' : '0' - } - // If the value is 1 or 0, we need to add something to the value, otherwise - // the next time we deserialize the tag values it will be converted to boolean - else if (value === '1' || value === '0') { - transformedTags[key] = `n__${value}` - } - // If the value is an array we create a tag for each array - // item ("tagName:arrayItem" = "1") - else if (Array.isArray(value)) { - value.forEach((item) => { - const tagName = `${key}:${item}` - transformedTags[tagName] = '1' - }) - } - // Otherwise just use the value - else { - transformedTags[key] = value - } - } - - return transformedTags - } - - /** - * Transforms the search query into a wallet query compatible with indy WQL. - * - * The format used by AFJ is almost the same as the indy query, with the exception of - * the encoding of values, however this is handled by the {@link IndyStorageService.transformToRecordTagValues} - * method. - */ - private indyQueryFromSearchQuery(query: Query): Record { - // eslint-disable-next-line prefer-const - let { $and, $or, $not, ...tags } = query - - $and = ($and as Query[] | undefined)?.map((q) => this.indyQueryFromSearchQuery(q)) - $or = ($or as Query[] | undefined)?.map((q) => this.indyQueryFromSearchQuery(q)) - $not = $not ? this.indyQueryFromSearchQuery($not as Query) : undefined - - const indyQuery = { - ...this.transformFromRecordTagValues(tags as unknown as TagsBase), - $and, - $or, - $not, - } - - return indyQuery - } - - private recordToInstance(record: WalletRecord, recordClass: BaseRecordConstructor): T { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - const instance = JsonTransformer.deserialize(record.value!, recordClass) - instance.id = record.id - - const tags = record.tags ? this.transformToRecordTagValues(record.tags) : {} - instance.replaceTags(tags) - - return instance - } - - /** @inheritDoc */ - public async save(agentContext: AgentContext, record: T) { - assertIndyWallet(agentContext.wallet) - - const value = JsonTransformer.serialize(record) - const tags = this.transformFromRecordTagValues(record.getTags()) as Record - - try { - await this.indy.addWalletRecord(agentContext.wallet.handle, record.type, record.id, value, tags) - } catch (error) { - // Record already exists - if (isIndyError(error, 'WalletItemAlreadyExists')) { - throw new RecordDuplicateError(`Record with id ${record.id} already exists`, { recordType: record.type }) - } - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** @inheritDoc */ - public async update(agentContext: AgentContext, record: T): Promise { - assertIndyWallet(agentContext.wallet) - - const value = JsonTransformer.serialize(record) - const tags = this.transformFromRecordTagValues(record.getTags()) as Record - - try { - await this.indy.updateWalletRecordValue(agentContext.wallet.handle, record.type, record.id, value) - await this.indy.updateWalletRecordTags(agentContext.wallet.handle, record.type, record.id, tags) - } catch (error) { - // Record does not exist - if (isIndyError(error, 'WalletItemNotFound')) { - throw new RecordNotFoundError(`record with id ${record.id} not found.`, { - recordType: record.type, - cause: error, - }) - } - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** @inheritDoc */ - public async delete(agentContext: AgentContext, record: T) { - assertIndyWallet(agentContext.wallet) - - try { - await this.indy.deleteWalletRecord(agentContext.wallet.handle, record.type, record.id) - } catch (error) { - // Record does not exist - if (isIndyError(error, 'WalletItemNotFound')) { - throw new RecordNotFoundError(`record with id ${record.id} not found.`, { - recordType: record.type, - cause: error, - }) - } - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** @inheritDoc */ - public async deleteById( - agentContext: AgentContext, - recordClass: BaseRecordConstructor, - id: string - ): Promise { - assertIndyWallet(agentContext.wallet) - - try { - await this.indy.deleteWalletRecord(agentContext.wallet.handle, recordClass.type, id) - } catch (error) { - if (isIndyError(error, 'WalletItemNotFound')) { - throw new RecordNotFoundError(`record with id ${id} not found.`, { - recordType: recordClass.type, - cause: error, - }) - } - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** @inheritDoc */ - public async getById(agentContext: AgentContext, recordClass: BaseRecordConstructor, id: string): Promise { - assertIndyWallet(agentContext.wallet) - - try { - const record = await this.indy.getWalletRecord( - agentContext.wallet.handle, - recordClass.type, - id, - IndyStorageService.DEFAULT_QUERY_OPTIONS - ) - return this.recordToInstance(record, recordClass) - } catch (error) { - if (isIndyError(error, 'WalletItemNotFound')) { - throw new RecordNotFoundError(`record with id ${id} not found.`, { - recordType: recordClass.type, - cause: error, - }) - } - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - /** @inheritDoc */ - public async getAll(agentContext: AgentContext, recordClass: BaseRecordConstructor): Promise { - assertIndyWallet(agentContext.wallet) - - const recordIterator = this.search( - agentContext.wallet, - recordClass.type, - {}, - IndyStorageService.DEFAULT_QUERY_OPTIONS - ) - const records = [] - for await (const record of recordIterator) { - records.push(this.recordToInstance(record, recordClass)) - } - return records - } - - /** @inheritDoc */ - public async findByQuery( - agentContext: AgentContext, - recordClass: BaseRecordConstructor, - query: Query - ): Promise { - assertIndyWallet(agentContext.wallet) - - const indyQuery = this.indyQueryFromSearchQuery(query) - - const recordIterator = this.search( - agentContext.wallet, - recordClass.type, - indyQuery, - IndyStorageService.DEFAULT_QUERY_OPTIONS - ) - const records = [] - for await (const record of recordIterator) { - records.push(this.recordToInstance(record, recordClass)) - } - return records - } - - private async *search( - wallet: IndyWallet, - type: string, - query: WalletQuery, - { limit = Infinity, ...options }: WalletSearchOptions & { limit?: number } - ) { - try { - const searchHandle = await this.indy.openWalletSearch(wallet.handle, type, query, options) - - let records: Indy.WalletRecord[] = [] - - // Allow max of 256 per fetch operation - const chunk = limit ? Math.min(256, limit) : 256 - - // Loop while limit not reached (or no limit specified) - while (!limit || records.length < limit) { - // Retrieve records - const recordsJson = await this.indy.fetchWalletSearchNextRecords(wallet.handle, searchHandle, chunk) - - if (recordsJson.records) { - records = [...records, ...recordsJson.records] - - for (const record of recordsJson.records) { - yield record - } - } - - // If the number of records returned is less than chunk - // It means we reached the end of the iterator (no more records) - if (!records.length || !recordsJson.records || recordsJson.records.length < chunk) { - await this.indy.closeWalletSearch(searchHandle) - - return - } - } - } catch (error) { - throw new IndySdkError(error, `Searching '${type}' records for query '${JSON.stringify(query)}' failed`) - } - } -} diff --git a/packages/core/src/storage/__tests__/IndyStorageService.test.ts b/packages/core/src/storage/__tests__/IndyStorageService.test.ts deleted file mode 100644 index bd61553b08..0000000000 --- a/packages/core/src/storage/__tests__/IndyStorageService.test.ts +++ /dev/null @@ -1,302 +0,0 @@ -import type { AgentContext } from '../../agent' -import type { TagsBase } from '../BaseRecord' -import type * as Indy from 'indy-sdk' - -import { agentDependencies, getAgentConfig, getAgentContext } from '../../../tests/helpers' -import { SigningProviderRegistry } from '../../crypto/signing-provider' -import { RecordDuplicateError, RecordNotFoundError } from '../../error' -import { IndyWallet } from '../../wallet/IndyWallet' -import { IndyStorageService } from '../IndyStorageService' - -import { TestRecord } from './TestRecord' - -describe('IndyStorageService', () => { - let wallet: IndyWallet - let indy: typeof Indy - let storageService: IndyStorageService - let agentContext: AgentContext - - beforeEach(async () => { - const agentConfig = getAgentConfig('IndyStorageServiceTest') - indy = agentConfig.agentDependencies.indy - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) - agentContext = getAgentContext({ - wallet, - agentConfig, - }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) - storageService = new IndyStorageService(agentConfig.agentDependencies) - }) - - afterEach(async () => { - await wallet.delete() - }) - - const insertRecord = async ({ id, tags }: { id?: string; tags?: TagsBase }) => { - const props = { - id, - foo: 'bar', - tags: tags ?? { myTag: 'foobar' }, - } - const record = new TestRecord(props) - await storageService.save(agentContext, record) - return record - } - - describe('tag transformation', () => { - it('should correctly transform tag values to string before storing', async () => { - const record = await insertRecord({ - id: 'test-id', - tags: { - someBoolean: true, - someOtherBoolean: false, - someStringValue: 'string', - anArrayValue: ['foo', 'bar'], - // booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0' - someStringNumberValue: '1', - anotherStringNumberValue: '0', - }, - }) - - const retrieveRecord = await indy.getWalletRecord(wallet.handle, record.type, record.id, { - retrieveType: true, - retrieveTags: true, - }) - - expect(retrieveRecord.tags).toEqual({ - someBoolean: '1', - someOtherBoolean: '0', - someStringValue: 'string', - 'anArrayValue:foo': '1', - 'anArrayValue:bar': '1', - someStringNumberValue: 'n__1', - anotherStringNumberValue: 'n__0', - }) - }) - - it('should correctly transform tag values from string after retrieving', async () => { - await indy.addWalletRecord(wallet.handle, TestRecord.type, 'some-id', '{}', { - someBoolean: '1', - someOtherBoolean: '0', - someStringValue: 'string', - 'anArrayValue:foo': '1', - 'anArrayValue:bar': '1', - // booleans are stored as '1' and '0' so we store the string values '1' and '0' as 'n__1' and 'n__0' - someStringNumberValue: 'n__1', - anotherStringNumberValue: 'n__0', - }) - - const record = await storageService.getById(agentContext, TestRecord, 'some-id') - - expect(record.getTags()).toEqual({ - someBoolean: true, - someOtherBoolean: false, - someStringValue: 'string', - anArrayValue: expect.arrayContaining(['bar', 'foo']), - someStringNumberValue: '1', - anotherStringNumberValue: '0', - }) - }) - }) - - describe('save()', () => { - it('should throw RecordDuplicateError if a record with the id already exists', async () => { - const record = await insertRecord({ id: 'test-id' }) - - return expect(() => storageService.save(agentContext, record)).rejects.toThrowError(RecordDuplicateError) - }) - - it('should save the record', async () => { - const record = await insertRecord({ id: 'test-id' }) - const found = await storageService.getById(agentContext, TestRecord, 'test-id') - - expect(record).toEqual(found) - }) - }) - - describe('getById()', () => { - it('should throw RecordNotFoundError if the record does not exist', async () => { - return expect(() => storageService.getById(agentContext, TestRecord, 'does-not-exist')).rejects.toThrowError( - RecordNotFoundError - ) - }) - - it('should return the record by id', async () => { - const record = await insertRecord({ id: 'test-id' }) - const found = await storageService.getById(agentContext, TestRecord, 'test-id') - - expect(found).toEqual(record) - }) - }) - - describe('update()', () => { - it('should throw RecordNotFoundError if the record does not exist', async () => { - const record = new TestRecord({ - id: 'test-id', - foo: 'test', - tags: { some: 'tag' }, - }) - - return expect(() => storageService.update(agentContext, record)).rejects.toThrowError(RecordNotFoundError) - }) - - it('should update the record', async () => { - const record = await insertRecord({ id: 'test-id' }) - - record.replaceTags({ ...record.getTags(), foo: 'bar' }) - record.foo = 'foobaz' - await storageService.update(agentContext, record) - - const retrievedRecord = await storageService.getById(agentContext, TestRecord, record.id) - expect(retrievedRecord).toEqual(record) - }) - }) - - describe('delete()', () => { - it('should throw RecordNotFoundError if the record does not exist', async () => { - const record = new TestRecord({ - id: 'test-id', - foo: 'test', - tags: { some: 'tag' }, - }) - - return expect(() => storageService.delete(agentContext, record)).rejects.toThrowError(RecordNotFoundError) - }) - - it('should delete the record', async () => { - const record = await insertRecord({ id: 'test-id' }) - await storageService.delete(agentContext, record) - - return expect(() => storageService.getById(agentContext, TestRecord, record.id)).rejects.toThrowError( - RecordNotFoundError - ) - }) - }) - - describe('getAll()', () => { - it('should retrieve all records', async () => { - const createdRecords = await Promise.all( - Array(5) - .fill(undefined) - .map((_, index) => insertRecord({ id: `record-${index}` })) - ) - - const records = await storageService.getAll(agentContext, TestRecord) - - expect(records).toEqual(expect.arrayContaining(createdRecords)) - }) - }) - - describe('findByQuery()', () => { - it('should retrieve all records that match the query', async () => { - const expectedRecord = await insertRecord({ tags: { myTag: 'foobar' } }) - await insertRecord({ tags: { myTag: 'notfoobar' } }) - - const records = await storageService.findByQuery(agentContext, TestRecord, { myTag: 'foobar' }) - - expect(records.length).toBe(1) - expect(records[0]).toEqual(expectedRecord) - }) - - it('finds records using $and statements', async () => { - const expectedRecord = await insertRecord({ tags: { myTag: 'foo', anotherTag: 'bar' } }) - await insertRecord({ tags: { myTag: 'notfoobar' } }) - - const records = await storageService.findByQuery(agentContext, TestRecord, { - $and: [{ myTag: 'foo' }, { anotherTag: 'bar' }], - }) - - expect(records.length).toBe(1) - expect(records[0]).toEqual(expectedRecord) - }) - - it('finds records using $or statements', async () => { - const expectedRecord = await insertRecord({ tags: { myTag: 'foo' } }) - const expectedRecord2 = await insertRecord({ tags: { anotherTag: 'bar' } }) - await insertRecord({ tags: { myTag: 'notfoobar' } }) - - const records = await storageService.findByQuery(agentContext, TestRecord, { - $or: [{ myTag: 'foo' }, { anotherTag: 'bar' }], - }) - - expect(records.length).toBe(2) - expect(records).toEqual(expect.arrayContaining([expectedRecord, expectedRecord2])) - }) - - it('finds records using $not statements', async () => { - const expectedRecord = await insertRecord({ tags: { myTag: 'foo' } }) - const expectedRecord2 = await insertRecord({ tags: { anotherTag: 'bar' } }) - await insertRecord({ tags: { myTag: 'notfoobar' } }) - - const records = await storageService.findByQuery(agentContext, TestRecord, { - $not: { myTag: 'notfoobar' }, - }) - - expect(records.length).toBe(2) - expect(records).toEqual(expect.arrayContaining([expectedRecord, expectedRecord2])) - }) - - it('correctly transforms an advanced query into a valid WQL query', async () => { - const indySpy = jest.fn() - const storageServiceWithoutIndy = new IndyStorageService({ - ...agentDependencies, - indy: { - openWalletSearch: indySpy, - fetchWalletSearchNextRecords: jest.fn(() => ({ records: undefined })), - closeWalletSearch: jest.fn(), - } as unknown as typeof Indy, - }) - - await storageServiceWithoutIndy.findByQuery(agentContext, TestRecord, { - $and: [ - { - $or: [{ myTag: true }, { myTag: false }], - }, - { - $and: [{ theNumber: '0' }, { theNumber: '1' }], - }, - ], - $or: [ - { - aValue: ['foo', 'bar'], - }, - ], - $not: { myTag: 'notfoobar' }, - }) - - const expectedQuery = { - $and: [ - { - $and: undefined, - $not: undefined, - $or: [ - { myTag: '1', $and: undefined, $or: undefined, $not: undefined }, - { myTag: '0', $and: undefined, $or: undefined, $not: undefined }, - ], - }, - { - $or: undefined, - $not: undefined, - $and: [ - { theNumber: 'n__0', $and: undefined, $or: undefined, $not: undefined }, - { theNumber: 'n__1', $and: undefined, $or: undefined, $not: undefined }, - ], - }, - ], - $or: [ - { - 'aValue:foo': '1', - 'aValue:bar': '1', - $and: undefined, - $or: undefined, - $not: undefined, - }, - ], - $not: { myTag: 'notfoobar', $and: undefined, $or: undefined, $not: undefined }, - } - - expect(indySpy).toBeCalledWith(expect.anything(), expect.anything(), expectedQuery, expect.anything()) - }) - }) -}) diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index b454c7963e..c1f05d5026 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -1,6 +1,5 @@ import type { Logger } from './logger' import type { AutoAcceptCredential } from './modules/credentials/models/CredentialAutoAcceptType' -import type { IndyPoolConfig } from './modules/ledger/IndyPool' import type { AutoAcceptProof } from './modules/proofs' import type { MediatorPickupStrategy } from './modules/routing' @@ -84,20 +83,6 @@ export interface InitConfig { */ autoAcceptCredentials?: AutoAcceptCredential - /** - * @deprecated configure `indyLedgers` on the `LedgerModule` class - * @note This setting will be ignored if the `LedgerModule` is manually configured as - * a module - */ - indyLedgers?: IndyPoolConfig[] - - /** - * @deprecated configure `connectToIndyLedgersOnStartup` on the `LedgerModule` class - * @note This setting will be ignored if the `LedgerModule` is manually configured as - * a module - */ - connectToIndyLedgersOnStartup?: boolean - /** * @deprecated configure `autoAcceptMediationRequests` on the `RecipientModule` class * @note This setting will be ignored if the `RecipientModule` is manually configured as diff --git a/packages/core/src/utils/__tests__/did.test.ts b/packages/core/src/utils/__tests__/did.test.ts deleted file mode 100644 index 3d7aa07792..0000000000 --- a/packages/core/src/utils/__tests__/did.test.ts +++ /dev/null @@ -1,188 +0,0 @@ -import { - getIndyDidFromVerificationMethod, - isAbbreviatedVerkey, - isDid, - isDidIdentifier, - isFullVerkey, - isSelfCertifiedDid, - isVerkey, -} from '../did' - -const validAbbreviatedVerkeys = [ - '~PKAYz8Ev4yoQgr2LaMAWFx', - '~Soy1augaQrQYtNZRRHsikB', - '~BUF7uxYTxZ6qYdZ4G9e1Gi', - '~DbZ4gkBqhFRVsT5P7BJqyZ', - '~4zmNTdG78iYyMAQdEQLrf8', -] - -const invalidAbbreviatedVerkeys = [ - '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvt', - '8jG2Bim1HNSybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', - 'ABUF7uxYTxZ6qYdZ4G9e1Gi', - '~Db3IgkBqhFRVsT5P7BJqyZ', - '~4zmNTlG78iYyMAQdEQLrf8', -] - -const validFullVerkeys = [ - '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvt', - '8jG2Bim1HNSybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', - '9wMLhw9SSxtTUyosrndMbvWY4TtDbVvRnMtzG2NysniP', - '6m2XT39vivJ7tLSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', - 'CAgL85iEecPNQMmxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', - 'MqXmB7cTsTXqyxDPBbrgu5EPqw61kouK1qjMvnoPa96', -] - -const invalidFullVerkeys = [ - '~PKAYz8Ev4yoQgr2LaMAWFx', - '~Soy1augaQrQYtNZRRHsikB', - '6YnVN5Qdb6mqimTRQcQmSXrHXKdTEdRn5YHZReezUTvta', - '6m2XT39vIvJ7tLSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', - 'CAgL85iEecPNQMlxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', -] - -const invalidVerkeys = [ - '6YnVN5Qdb6mqimTIQcQmSXrHXKdTEdRn5YHZReezUTvta', - '6m2XT39vIvJ7tlSxNPM8siMnhYCZcdMxbkTcJDSzAQTu', - 'CAgL85iEecPNQMlxQ1hgbqczwq7SAerQ8RbWTRtC7SoK', - '6YnVN5Qdb6mqilTRQcQmSXrHXKdTEdRn5YHZReezUTvt', - '8jG2Bim1HNIybCTdKBRppP4PCQSSijx1pBnreqsdo8JG', - 'ABUF7uxYTxZ6qYdZ4G9e1Gi', - '~Db3IgkBqhFRVsT5P7BJqyZ', - '~4zmNTlG78IYyMAQdEQLrf8', - 'randomverkey', -] - -const validDids = [ - 'did:indy:BBPoJqRKatdcfLEAFL7exC', - 'did:sov:N8NQHLtCKfPmWMgCSdfa7h', - 'did:random:FBSegXg6AsF8J73kx22gjk', - 'did:sov:8u2b8ZH6sHeWfvphyQuHCL', - 'did:ethr:0xb9c5714089478a327f09197987f16f9e5d936e8a', - 'did:btcr:xyv2-xzpq-q9wa-p7t', -] - -const invalidDids = [ - '6YnVN5Qdb6mqimTIQcQmSXrHXKdTEdRn5YHZReezUTvta', - 'did:BBPoJqRKatdcfLEAFL7exC', - 'sov:N8NQHLtCKfPmWMgCSdfa7h', - '8kyt-fzzq-qpqq-ljsc-5l', - 'did:test1:N8NQHLtCKfPmWMgCSdfa7h', - 'deid:ethr:9noxi4nL4SiJAsFcMLp2U4', -] - -const validDidIdentifiers = [ - '8kyt-fzzq-qpqq-ljsc-5l', - 'fEMDp21GvaafC5hXLaLHf', - '9noxi4nL4SiJAsFcMLp2U4', - 'QdAJFDpbVoHYrUpNAMe3An', - 'B9Y3e8PUKrM1ShumWU36xW', - '0xf3beac30c498d9e26865f34fcaa57dbb935b0d74', -] - -const invalidDidIdentifiers = [ - '6YnVN5Qdb6mqimTIQcQmSXrHXKdTEdRn5YHZReezUTvt/a', - 'did:BBPoJqRKatdcfLEAFL7exC', - 'sov:N8NQHLtCKfPmWMgCSdfa7h', - 'did:test1:N8NQHLtCKfPmWMgCSdfa7h', - 'deid:ethr:9noxi4nL4SiJAsFcMLp2U4', -] - -const verificationMethod = { - id: 'did:key:z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr#z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr', - type: 'Ed25519VerificationKey2018', - controller: 'did:key:z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr', - publicKeyBase58: 'VExfvq3kqkbAfCA3PZ8CRbzH2A3HyV9FWwdSq6WhtgU', -} - -const invalidVerificationMethod = [ - { - id: 'did:key:z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr#z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr', - type: 'Ed25519VerificationKey2018', - controller: 'did:key:z6MkewW1GB5V6PF4HA2rixWy3X9z6bRthrjVwXrZH74Xd7Tr', - publicKeyBase58: '', - }, -] - -const indyDid = 'tpF86Zd1cf9JdVmqKdMW2' - -describe('Utils | Did', () => { - describe('isSelfCertifiedDid()', () => { - test('returns true if the verkey is abbreviated', () => { - expect(isSelfCertifiedDid('PW8ZHpNupeWXbmpPWog6Ki', '~QQ5jiH1dgXPAnvHdJvazn9')).toBe(true) - }) - - test('returns true if the verkey is not abbreviated and the did is generated from the verkey', () => { - expect(isSelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'HyEoPRNvC7q4jj5joUo8AWYtxbNccbEnTAeuMYkpmNS2')).toBe(true) - }) - - test('returns false if the verkey is not abbreviated and the did is not generated from the verkey', () => { - expect(isSelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'AcU7DnRqoXGYATD6VqsRq4eHuz55gdM3uzFBEhFd6rGh')).toBe(false) - }) - }) - - describe('isAbbreviatedVerkey()', () => { - test.each(validAbbreviatedVerkeys)('returns true when valid abbreviated verkey "%s" is passed in', (verkey) => { - expect(isAbbreviatedVerkey(verkey)).toBe(true) - }) - - test.each(invalidAbbreviatedVerkeys)( - 'returns false when invalid abbreviated verkey "%s" is passed in', - (verkey) => { - expect(isAbbreviatedVerkey(verkey)).toBe(false) - } - ) - }) - - describe('isFullVerkey()', () => { - test.each(validFullVerkeys)('returns true when valid full verkey "%s" is passed in', (verkey) => { - expect(isFullVerkey(verkey)).toBe(true) - }) - - test.each(invalidFullVerkeys)('returns false when invalid full verkey "%s" is passed in', (verkey) => { - expect(isFullVerkey(verkey)).toBe(false) - }) - }) - - describe('isVerkey()', () => { - const validVerkeys = [...validAbbreviatedVerkeys, ...validFullVerkeys] - - test.each(validVerkeys)('returns true when valid verkey "%s" is passed in', (verkey) => { - expect(isVerkey(verkey)).toBe(true) - }) - - test.each(invalidVerkeys)('returns false when invalid verkey "%s" is passed in', (verkey) => { - expect(isVerkey(verkey)).toBe(false) - }) - }) - - describe('isDid()', () => { - test.each(validDids)('returns true when valid did "%s" is passed in', (did) => { - expect(isDid(did)).toBe(true) - }) - - test.each(invalidDids)('returns false when invalid did "%s" is passed in', (did) => { - expect(isDid(did)).toBe(false) - }) - }) - - describe('isDidIdentifier()', () => { - test.each(validDidIdentifiers)('returns true when valid did identifier "%s" is passed in', (didIdentifier) => { - expect(isDidIdentifier(didIdentifier)).toBe(true) - }) - - test.each(invalidDidIdentifiers)('returns false when invalid did identifier "%s" is passed in', (didIdentifier) => { - expect(isDidIdentifier(didIdentifier)).toBe(false) - }) - }) - - describe('getIndyDidFromVerificationMethod()', () => { - expect(getIndyDidFromVerificationMethod(verificationMethod)).toBe(indyDid) - - test.each(invalidVerificationMethod)('throw error when invalid public key in verification method', (method) => { - expect(() => { - getIndyDidFromVerificationMethod(method) - }).toThrow() - }) - }) -}) diff --git a/packages/core/src/utils/__tests__/indyIdentifiers.test.ts b/packages/core/src/utils/__tests__/indyIdentifiers.test.ts deleted file mode 100644 index 8da274a789..0000000000 --- a/packages/core/src/utils/__tests__/indyIdentifiers.test.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { - isQualifiedIndyIdentifier, - getQualifiedIndyCredentialDefinitionId, - getQualifiedIndySchemaId, - getLegacyCredentialDefinitionId, - getLegacySchemaId, -} from '../indyIdentifiers' - -const indyNamespace = 'some:staging' -const did = 'q7ATwTYbQDgiigVijUAej' -const qualifiedSchemaId = `did:indy:${indyNamespace}:${did}/anoncreds/v0/SCHEMA/awesomeSchema/4.2.0` -const qualifiedCredentialDefinitionId = `did:indy:${indyNamespace}:${did}/anoncreds/v0/CLAIM_DEF/99/sth` -const unqualifiedSchemaId = `${did}:2:awesomeSchema:4.2.0` -const unqualifiedCredentialDefinitionId = `${did}:3:CL:99:sth` - -describe('Mangle indy identifiers', () => { - test('is a qualified identifier', async () => { - expect(isQualifiedIndyIdentifier(qualifiedSchemaId)).toBe(true) - }) - - test('is NOT a qualified identifier', async () => { - expect(isQualifiedIndyIdentifier(did)).toBe(false) - }) - - describe('get the qualified identifier', () => { - it('should return the qualified identifier if the identifier is already qualified', () => { - expect(getQualifiedIndyCredentialDefinitionId(indyNamespace, qualifiedCredentialDefinitionId)).toBe( - qualifiedCredentialDefinitionId - ) - }) - - it('should return the qualified identifier for a credential definition', () => { - expect(getQualifiedIndyCredentialDefinitionId(indyNamespace, unqualifiedCredentialDefinitionId)).toBe( - qualifiedCredentialDefinitionId - ) - }) - - it('should return the qualified identifier for a schema', () => { - expect(getQualifiedIndySchemaId(indyNamespace, qualifiedSchemaId)).toBe(qualifiedSchemaId) - }) - - it('should return the qualified identifier for a schema', () => { - expect(getQualifiedIndySchemaId(indyNamespace, unqualifiedSchemaId)).toBe(qualifiedSchemaId) - }) - }) - - // generateSchemaId - it('Should return a valid schema ID given did name and version', () => { - const did = '12345', - name = 'backbench', - version = '420' - expect(getLegacySchemaId(did, name, version)).toEqual('12345:2:backbench:420') - }) - - // generateCredentialDefinitionId - it('Should return a valid schema ID given did name and version', () => { - const did = '12345', - seqNo = 420, - tag = 'someTag' - expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') - }) -}) diff --git a/packages/core/src/utils/did.ts b/packages/core/src/utils/did.ts index 74f346560d..d21405252d 100644 --- a/packages/core/src/utils/did.ts +++ b/packages/core/src/utils/did.ts @@ -1,53 +1,4 @@ -/** - * Based on DidUtils implementation in Aries Framework .NET - * @see: https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Utils/DidUtils.cs - * - * Some context about full verkeys versus abbreviated verkeys: - * A standard verkey is 32 bytes, and by default in Indy the DID is chosen as the first 16 bytes of that key, before base58 encoding. - * An abbreviated verkey replaces the first 16 bytes of the verkey with ~ when it matches the DID. - * - * When a full verkey is used to register on the ledger, this is stored as a full verkey on the ledger and also returned from the ledger as a full verkey. - * The same applies to an abbreviated verkey. If an abbreviated verkey is used to register on the ledger, this is stored as an abbreviated verkey on the ledger and also returned from the ledger as an abbreviated verkey. - * - * For this reason we need some methods to check whether verkeys are full or abbreviated, so we can align this with `indy.abbreviateVerkey` - * - * Aries Framework .NET also abbreviates verkey before sending to ledger: - * https://github.com/hyperledger/aries-framework-dotnet/blob/f90eaf9db8548f6fc831abea917e906201755763/src/Hyperledger.Aries/Ledger/DefaultLedgerService.cs#L139-L147 - */ - -import type { VerificationMethod } from './../modules/dids/domain/verificationMethod/VerificationMethod' - import { TypedArrayEncoder } from './TypedArrayEncoder' -import { Buffer } from './buffer' - -export const FULL_VERKEY_REGEX = /^[1-9A-HJ-NP-Za-km-z]{43,44}$/ -export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ -export const VERKEY_REGEX = new RegExp(`${FULL_VERKEY_REGEX.source}|${ABBREVIATED_VERKEY_REGEX.source}`) -export const DID_REGEX = /^did:([a-z]+):([a-zA-z\d]+)/ -export const DID_IDENTIFIER_REGEX = /^[a-zA-z\d-]+$/ - -/** - * Check whether the did is a self certifying did. If the verkey is abbreviated this method - * will always return true. Make sure that the verkey you pass in this method belongs to the - * did passed in - * - * @return Boolean indicating whether the did is self certifying - */ -export function isSelfCertifiedDid(did: string, verkey: string): boolean { - // If the verkey is Abbreviated, it means the full verkey - // is the did + the verkey - if (isAbbreviatedVerkey(verkey)) { - return true - } - - const didFromVerkey = indyDidFromPublicKeyBase58(verkey) - - if (didFromVerkey === did) { - return true - } - - return false -} export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { const buffer = TypedArrayEncoder.fromBase58(publicKeyBase58) @@ -56,108 +7,3 @@ export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { return did } - -export function getFullVerkey(did: string, verkey: string) { - if (isFullVerkey(verkey)) return verkey - - // Did could have did:xxx prefix, only take the last item after : - const id = did.split(':').pop() ?? did - // Verkey is prefixed with ~ if abbreviated - const verkeyWithoutTilde = verkey.slice(1) - - // Create base58 encoded public key (32 bytes) - return TypedArrayEncoder.toBase58( - Buffer.concat([ - // Take did identifier (16 bytes) - TypedArrayEncoder.fromBase58(id), - // Concat the abbreviated verkey (16 bytes) - TypedArrayEncoder.fromBase58(verkeyWithoutTilde), - ]) - ) -} - -/** - * Extract did from schema id - */ -export function didFromSchemaId(schemaId: string) { - const [did] = schemaId.split(':') - - return did -} - -/** - * Extract did from credential definition id - */ -export function didFromCredentialDefinitionId(credentialDefinitionId: string) { - const [did] = credentialDefinitionId.split(':') - - return did -} - -/** - * Extract did from revocation registry definition id - */ -export function didFromRevocationRegistryDefinitionId(revocationRegistryId: string) { - const [did] = revocationRegistryId.split(':') - - return did -} - -/** - * Check a base58 encoded string against a regex expression to determine if it is a full valid verkey - * @param verkey Base58 encoded string representation of a verkey - * @return Boolean indicating if the string is a valid verkey - */ -export function isFullVerkey(verkey: string): boolean { - return FULL_VERKEY_REGEX.test(verkey) -} - -/** - * Check a base58 encoded string against a regex expression to determine if it is a valid abbreviated verkey - * @param verkey Base58 encoded string representation of an abbreviated verkey - * @returns Boolean indicating if the string is a valid abbreviated verkey - */ -export function isAbbreviatedVerkey(verkey: string): boolean { - return ABBREVIATED_VERKEY_REGEX.test(verkey) -} - -/** - * Check a base58 encoded string to determine if it is a valid verkey - * @param verkey Base58 encoded string representation of a verkey - * @returns Boolean indicating if the string is a valid verkey - */ -export function isVerkey(verkey: string): boolean { - return VERKEY_REGEX.test(verkey) -} - -/** - * Check a string to determine if it is a valid did - * @param did - * @return Boolean indicating if the string is a valid did - */ -export function isDid(did: string): boolean { - return DID_REGEX.test(did) -} - -/** - * Check a string to determine if it is a valid did identifier. - * @param identifier Did identifier. This is a did without the did:method part - * @return Boolean indicating if the string is a valid did identifier - */ -export function isDidIdentifier(identifier: string): boolean { - return DID_IDENTIFIER_REGEX.test(identifier) -} - -/** - * Get indy did from verification method - * @param verificationMethod - * @returns indy did - */ -export function getIndyDidFromVerificationMethod(verificationMethod: VerificationMethod): string { - if (!verificationMethod?.publicKeyBase58) { - throw new Error(`Unable to get publicKeyBase58 from verification method`) - } - const buffer = TypedArrayEncoder.fromBase58(verificationMethod.publicKeyBase58) - const did = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) - return did -} diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 8875930e15..58edb19a94 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -6,12 +6,10 @@ export * from './buffer' export * from './MultiHashEncoder' export * from './JWE' export * from './regex' -export * from './indyProofRequest' export * from './VarintEncoder' export * from './Hasher' export * from './validators' export * from './type' -export * from './indyIdentifiers' export * from './deepEquality' export * from './objectEquality' export * from './MessageValidator' diff --git a/packages/core/src/utils/indyIdentifiers.ts b/packages/core/src/utils/indyIdentifiers.ts deleted file mode 100644 index 0d6343a3e7..0000000000 --- a/packages/core/src/utils/indyIdentifiers.ts +++ /dev/null @@ -1,53 +0,0 @@ -/** - * - * @see For the definitions below see also: https://hyperledger.github.io/indy-did-method/#indy-did-method-identifiers - * - */ -export type Did = 'did' -export type DidIndyMethod = 'indy' -// Maybe this can be typed more strictly than string. Choosing string for now as this can be eg just `sovrin` or eg `sovrin:staging` -export type DidIndyNamespace = string -// NOTE: because of the ambiguous nature - whether there is a colon or not within DidIndyNamespace this is the substring after the ***last*** colon -export type NamespaceIdentifier = string - -// TODO: This template literal type can possibly be improved. This version leaves the substrings as potentially undefined -export type IndyNamespace = `${Did}:${DidIndyMethod}:${DidIndyNamespace}:${NamespaceIdentifier}` - -export function isQualifiedIndyIdentifier(identifier: string | undefined): identifier is IndyNamespace { - if (!identifier || identifier === '') return false - return identifier.startsWith('did:indy:') -} - -export function getQualifiedIndyCredentialDefinitionId( - indyNamespace: string, - unqualifiedCredentialDefinitionId: string -): IndyNamespace { - if (isQualifiedIndyIdentifier(unqualifiedCredentialDefinitionId)) return unqualifiedCredentialDefinitionId - - // 5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npbd - const [did, , , seqNo, tag] = unqualifiedCredentialDefinitionId.split(':') - - return `did:indy:${indyNamespace}:${did}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` -} - -/** - * - * @see https://hyperledger.github.io/indy-did-method/#schema - * - */ -export function getQualifiedIndySchemaId(indyNamespace: string, schemaId: string): IndyNamespace { - if (isQualifiedIndyIdentifier(schemaId)) return schemaId - - // F72i3Y3Q4i466efjYJYCHM:2:npdb:4.3.4 - const [did, , schemaName, schemaVersion] = schemaId.split(':') - - return `did:indy:${indyNamespace}:${did}/anoncreds/v0/SCHEMA/${schemaName}/${schemaVersion}` -} - -export function getLegacySchemaId(unqualifiedDid: string, name: string, version: string) { - return `${unqualifiedDid}:2:${name}:${version}` -} - -export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: number, tag: string) { - return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` -} diff --git a/packages/core/src/utils/indyProofRequest.ts b/packages/core/src/utils/indyProofRequest.ts deleted file mode 100644 index df52b72cdc..0000000000 --- a/packages/core/src/utils/indyProofRequest.ts +++ /dev/null @@ -1,32 +0,0 @@ -import type { ProofRequest } from '../modules/proofs/formats/indy/models/ProofRequest' - -import { AriesFrameworkError } from '../error/AriesFrameworkError' - -function attributeNamesToArray(proofRequest: ProofRequest) { - // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array - // containing all attribute names from the requested attributes. - return Array.from(proofRequest.requestedAttributes.values()).reduce( - (names, a) => [...names, ...(a.name ? [a.name] : a.names ? a.names : [])], - [] - ) -} - -function predicateNamesToArray(proofRequest: ProofRequest) { - return Array.from(new Set(Array.from(proofRequest.requestedPredicates.values()).map((a) => a.name))) -} - -function assertNoDuplicates(predicates: string[], attributeNames: string[]) { - const duplicates = predicates.filter((item) => attributeNames.indexOf(item) !== -1) - if (duplicates.length > 0) { - throw new AriesFrameworkError( - `The proof request contains duplicate predicates and attributes: ${duplicates.toString()}` - ) - } -} - -// TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. -export function assertNoDuplicateGroupsNamesInProofRequest(proofRequest: ProofRequest) { - const attributes = attributeNamesToArray(proofRequest) - const predicates = predicateNamesToArray(proofRequest) - assertNoDuplicates(predicates, attributes) -} diff --git a/packages/core/src/wallet/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts deleted file mode 100644 index 07c5e74978..0000000000 --- a/packages/core/src/wallet/IndyWallet.test.ts +++ /dev/null @@ -1,125 +0,0 @@ -import type { WalletConfig } from '../types' - -import { SIGNATURE_LENGTH as ED25519_SIGNATURE_LENGTH } from '@stablelib/ed25519' - -import { agentDependencies } from '../../tests/helpers' -import testLogger from '../../tests/logger' -import { KeyType } from '../crypto' -import { SigningProviderRegistry } from '../crypto/signing-provider' -import { KeyDerivationMethod } from '../types' -import { TypedArrayEncoder } from '../utils' - -import { IndyWallet } from './IndyWallet' -import { WalletError } from './error' - -// use raw key derivation method to speed up wallet creating / opening / closing between tests -const walletConfig: WalletConfig = { - id: 'Wallet: IndyWalletTest', - // generated using indy.generateWalletKey - key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', - keyDerivationMethod: KeyDerivationMethod.Raw, -} - -const walletConfigWithMasterSecretId: WalletConfig = { - id: 'Wallet: WalletTestWithMasterSecretId', - // generated using indy.generateWalletKey - key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', - keyDerivationMethod: KeyDerivationMethod.Raw, - masterSecretId: 'customMasterSecretId', -} - -describe('IndyWallet', () => { - let indyWallet: IndyWallet - - const seed = 'sample-seed' - const message = TypedArrayEncoder.fromString('sample-message') - - beforeEach(async () => { - indyWallet = new IndyWallet(agentDependencies, testLogger, new SigningProviderRegistry([])) - await indyWallet.createAndOpen(walletConfig) - }) - - afterEach(async () => { - await indyWallet.delete() - }) - - test('Get the public DID', async () => { - await indyWallet.initPublicDid({ seed: '000000000000000000000000Trustee9' }) - expect(indyWallet.publicDid).toMatchObject({ - did: expect.any(String), - verkey: expect.any(String), - }) - }) - - test('Get the Master Secret', () => { - expect(indyWallet.masterSecretId).toEqual('Wallet: IndyWalletTest') - }) - - test('Get the wallet handle', () => { - expect(indyWallet.handle).toEqual(expect.any(Number)) - }) - - test('Initializes a public did', async () => { - await indyWallet.initPublicDid({ seed: '00000000000000000000000Forward01' }) - - expect(indyWallet.publicDid).toEqual({ - did: 'DtWRdd6C5dN5vpcN6XRAvu', - verkey: '82RBSn3heLgXzZd74UsMC8Q8YRfEEhQoAM7LUqE6bevJ', - }) - }) - - test('Generate Nonce', async () => { - await expect(indyWallet.generateNonce()).resolves.toEqual(expect.any(String)) - }) - - test('Create ed25519 keypair', async () => { - await expect( - indyWallet.createKey({ seed: '2103de41b4ae37e8e28586d84a342b67', keyType: KeyType.Ed25519 }) - ).resolves.toMatchObject({ - keyType: KeyType.Ed25519, - }) - }) - - test('Fail to create x25519 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.X25519 })).rejects.toThrowError(WalletError) - }) - - test('Create a signature with a ed25519 keypair', async () => { - const ed25519Key = await indyWallet.createKey({ keyType: KeyType.Ed25519 }) - const signature = await indyWallet.sign({ - data: message, - key: ed25519Key, - }) - expect(signature.length).toStrictEqual(ED25519_SIGNATURE_LENGTH) - }) - - test('Verify a signed message with a ed25519 publicKey', async () => { - const ed25519Key = await indyWallet.createKey({ keyType: KeyType.Ed25519 }) - const signature = await indyWallet.sign({ - data: message, - key: ed25519Key, - }) - await expect(indyWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) - }) - - test('masterSecretId is equal to wallet ID by default', async () => { - expect(indyWallet.masterSecretId).toEqual(walletConfig.id) - }) -}) - -describe('IndyWallet with custom Master Secret Id', () => { - let indyWallet: IndyWallet - - beforeEach(async () => { - indyWallet = new IndyWallet(agentDependencies, testLogger, new SigningProviderRegistry([])) - await indyWallet.createAndOpen(walletConfigWithMasterSecretId) - }) - - afterEach(async () => { - await indyWallet.delete() - }) - - test('masterSecretId is set by config', async () => { - expect(indyWallet.masterSecretId).toEqual(walletConfigWithMasterSecretId.masterSecretId) - }) -}) diff --git a/packages/core/src/wallet/IndyWallet.ts b/packages/core/src/wallet/IndyWallet.ts deleted file mode 100644 index 0bef6447d6..0000000000 --- a/packages/core/src/wallet/IndyWallet.ts +++ /dev/null @@ -1,690 +0,0 @@ -import type { - WalletCreateKeyOptions, - DidConfig, - DidInfo, - WalletSignOptions, - UnpackedMessageContext, - WalletVerifyOptions, - Wallet, -} from './Wallet' -import type { KeyPair } from '../crypto/signing-provider/SigningProvider' -import type { - EncryptedMessage, - KeyDerivationMethod, - WalletConfig, - WalletConfigRekey, - WalletExportImportConfig, -} from '../types' -import type { Buffer } from '../utils/buffer' -import type { default as Indy, WalletStorageConfig } from 'indy-sdk' - -import { inject, injectable } from 'tsyringe' - -import { AgentDependencies } from '../agent/AgentDependencies' -import { InjectionSymbols } from '../constants' -import { KeyType } from '../crypto' -import { Key } from '../crypto/Key' -import { SigningProviderRegistry } from '../crypto/signing-provider/SigningProviderRegistry' -import { AriesFrameworkError, IndySdkError, RecordDuplicateError, RecordNotFoundError } from '../error' -import { Logger } from '../logger' -import { TypedArrayEncoder } from '../utils' -import { JsonEncoder } from '../utils/JsonEncoder' -import { isError } from '../utils/error' -import { isIndyError } from '../utils/indyError' - -import { WalletDuplicateError, WalletError, WalletNotFoundError } from './error' -import { WalletInvalidKeyError } from './error/WalletInvalidKeyError' - -@injectable() -export class IndyWallet implements Wallet { - private walletConfig?: WalletConfig - private walletHandle?: number - - private logger: Logger - private signingKeyProviderRegistry: SigningProviderRegistry - private publicDidInfo: DidInfo | undefined - private indy: typeof Indy - - public constructor( - @inject(InjectionSymbols.AgentDependencies) agentDependencies: AgentDependencies, - @inject(InjectionSymbols.Logger) logger: Logger, - signingKeyProviderRegistry: SigningProviderRegistry - ) { - this.logger = logger - this.signingKeyProviderRegistry = signingKeyProviderRegistry - this.indy = agentDependencies.indy - } - - public get isProvisioned() { - return this.walletConfig !== undefined - } - - public get isInitialized() { - return this.walletHandle !== undefined - } - - /** - * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be - * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but - * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather - * use the `DidsModule`. - */ - public get publicDid() { - return this.publicDidInfo - } - - public get handle() { - if (!this.walletHandle) { - throw new AriesFrameworkError( - 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' - ) - } - - return this.walletHandle - } - - public get masterSecretId() { - if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { - throw new AriesFrameworkError( - 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' - ) - } - - return this.walletConfig?.masterSecretId ?? this.walletConfig.id - } - - /** - * Dispose method is called when an agent context is disposed. - */ - public async dispose() { - if (this.isInitialized) { - await this.close() - } - } - - private walletStorageConfig(walletConfig: WalletConfig): Indy.WalletConfig { - const walletStorageConfig: Indy.WalletConfig = { - id: walletConfig.id, - storage_type: walletConfig.storage?.type, - } - - if (walletConfig.storage?.config) { - walletStorageConfig.storage_config = walletConfig.storage?.config as WalletStorageConfig - } - - return walletStorageConfig - } - - private walletCredentials( - walletConfig: WalletConfig, - rekey?: string, - rekeyDerivation?: KeyDerivationMethod - ): Indy.OpenWalletCredentials { - const walletCredentials: Indy.OpenWalletCredentials = { - key: walletConfig.key, - key_derivation_method: walletConfig.keyDerivationMethod, - } - if (rekey) { - walletCredentials.rekey = rekey - } - if (rekeyDerivation) { - walletCredentials.rekey_derivation_method = rekeyDerivation - } - if (walletConfig.storage?.credentials) { - walletCredentials.storage_credentials = walletConfig.storage?.credentials as Record - } - - return walletCredentials - } - - /** - * @throws {WalletDuplicateError} if the wallet already exists - * @throws {WalletError} if another error occurs - */ - public async create(walletConfig: WalletConfig): Promise { - await this.createAndOpen(walletConfig) - await this.close() - } - - /** - * @throws {WalletDuplicateError} if the wallet already exists - * @throws {WalletError} if another error occurs - */ - public async createAndOpen(walletConfig: WalletConfig): Promise { - this.logger.debug(`Creating wallet '${walletConfig.id}' using SQLite storage`) - - try { - await this.indy.createWallet(this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig)) - this.walletConfig = walletConfig - - // We usually want to create master secret only once, therefore, we can to do so when creating a wallet. - await this.open(walletConfig) - - // We need to open wallet before creating master secret because we need wallet handle here. - await this.createMasterSecret(this.handle, this.masterSecretId) - } catch (error) { - // If an error ocurred while creating the master secret, we should close the wallet - if (this.isInitialized) await this.close() - - if (isIndyError(error, 'WalletAlreadyExistsError')) { - const errorMessage = `Wallet '${walletConfig.id}' already exists` - this.logger.debug(errorMessage) - - throw new WalletDuplicateError(errorMessage, { - walletType: 'IndyWallet', - cause: error, - }) - } else { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - const errorMessage = `Error creating wallet '${walletConfig.id}'` - this.logger.error(errorMessage, { - error, - errorMessage: error.message, - }) - - throw new WalletError(errorMessage, { cause: error }) - } - } - - this.logger.debug(`Successfully created wallet '${walletConfig.id}'`) - } - - /** - * @throws {WalletNotFoundError} if the wallet does not exist - * @throws {WalletError} if another error occurs - */ - public async open(walletConfig: WalletConfig): Promise { - await this._open(walletConfig) - } - - /** - * @throws {WalletNotFoundError} if the wallet does not exist - * @throws {WalletError} if another error occurs - */ - public async rotateKey(walletConfig: WalletConfigRekey): Promise { - if (!walletConfig.rekey) { - throw new WalletError('Wallet rekey undefined!. Please specify the new wallet key') - } - await this._open( - { - id: walletConfig.id, - key: walletConfig.key, - keyDerivationMethod: walletConfig.keyDerivationMethod, - }, - walletConfig.rekey, - walletConfig.rekeyDerivationMethod - ) - } - - /** - * @throws {WalletNotFoundError} if the wallet does not exist - * @throws {WalletError} if another error occurs - */ - private async _open( - walletConfig: WalletConfig, - rekey?: string, - rekeyDerivation?: KeyDerivationMethod - ): Promise { - if (this.walletHandle) { - throw new WalletError( - 'Wallet instance already opened. Close the currently opened wallet before re-opening the wallet' - ) - } - - try { - this.walletHandle = await this.indy.openWallet( - this.walletStorageConfig(walletConfig), - this.walletCredentials(walletConfig, rekey, rekeyDerivation) - ) - if (rekey) { - this.walletConfig = { ...walletConfig, key: rekey, keyDerivationMethod: rekeyDerivation } - } else { - this.walletConfig = walletConfig - } - } catch (error) { - if (isIndyError(error, 'WalletNotFoundError')) { - const errorMessage = `Wallet '${walletConfig.id}' not found` - this.logger.debug(errorMessage) - - throw new WalletNotFoundError(errorMessage, { - walletType: 'IndyWallet', - cause: error, - }) - } else if (isIndyError(error, 'WalletAccessFailed')) { - const errorMessage = `Incorrect key for wallet '${walletConfig.id}'` - this.logger.debug(errorMessage) - throw new WalletInvalidKeyError(errorMessage, { - walletType: 'IndyWallet', - cause: error, - }) - } else { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - const errorMessage = `Error opening wallet '${walletConfig.id}': ${error.message}` - this.logger.error(errorMessage, { - error, - errorMessage: error.message, - }) - - throw new WalletError(errorMessage, { cause: error }) - } - } - - this.logger.debug(`Wallet '${walletConfig.id}' opened with handle '${this.handle}'`) - } - - /** - * @throws {WalletNotFoundError} if the wallet does not exist - * @throws {WalletError} if another error occurs - */ - public async delete(): Promise { - if (!this.walletConfig) { - throw new WalletError( - 'Can not delete wallet that does not have wallet config set. Make sure to call create wallet before deleting the wallet' - ) - } - - this.logger.info(`Deleting wallet '${this.walletConfig.id}'`) - - if (this.walletHandle) { - await this.close() - } - - try { - await this.indy.deleteWallet( - this.walletStorageConfig(this.walletConfig), - this.walletCredentials(this.walletConfig) - ) - } catch (error) { - if (isIndyError(error, 'WalletNotFoundError')) { - const errorMessage = `Error deleting wallet: wallet '${this.walletConfig.id}' not found` - this.logger.debug(errorMessage) - - throw new WalletNotFoundError(errorMessage, { - walletType: 'IndyWallet', - cause: error, - }) - } else { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - const errorMessage = `Error deleting wallet '${this.walletConfig.id}': ${error.message}` - this.logger.error(errorMessage, { - error, - errorMessage: error.message, - }) - - throw new WalletError(errorMessage, { cause: error }) - } - } - } - - public async export(exportConfig: WalletExportImportConfig) { - try { - this.logger.debug(`Exporting wallet ${this.walletConfig?.id} to path ${exportConfig.path}`) - await this.indy.exportWallet(this.handle, exportConfig) - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - const errorMessage = `Error exporting wallet: ${error.message}` - this.logger.error(errorMessage, { - error, - }) - - throw new WalletError(errorMessage, { cause: error }) - } - } - - public async import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig) { - try { - this.logger.debug(`Importing wallet ${walletConfig.id} from path ${importConfig.path}`) - await this.indy.importWallet( - { id: walletConfig.id }, - { key: walletConfig.key, key_derivation_method: walletConfig.keyDerivationMethod }, - importConfig - ) - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - const errorMessage = `Error importing wallet': ${error.message}` - this.logger.error(errorMessage, { - error, - }) - - throw new WalletError(errorMessage, { cause: error }) - } - } - - /** - * @throws {WalletError} if the wallet is already closed or another error occurs - */ - public async close(): Promise { - this.logger.debug(`Closing wallet ${this.walletConfig?.id}`) - if (!this.walletHandle) { - throw new WalletError('Wallet is in invalid state, you are trying to close wallet that has no `walletHandle`.') - } - - try { - await this.indy.closeWallet(this.walletHandle) - this.walletHandle = undefined - this.publicDidInfo = undefined - } catch (error) { - if (isIndyError(error, 'WalletInvalidHandle')) { - const errorMessage = `Error closing wallet: wallet already closed` - this.logger.debug(errorMessage) - - throw new WalletError(errorMessage, { - cause: error, - }) - } else { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - const errorMessage = `Error closing wallet': ${error.message}` - this.logger.error(errorMessage, { - error, - errorMessage: error.message, - }) - - throw new WalletError(errorMessage, { cause: error }) - } - } - } - - /** - * Create master secret with specified id in currently opened wallet. - * - * If a master secret by this id already exists in the current wallet, the method - * will return without doing anything. - * - * @throws {WalletError} if an error occurs - */ - private async createMasterSecret(walletHandle: number, masterSecretId: string): Promise { - this.logger.debug(`Creating master secret with id '${masterSecretId}' in wallet with handle '${walletHandle}'`) - - try { - await this.indy.proverCreateMasterSecret(walletHandle, masterSecretId) - - return masterSecretId - } catch (error) { - if (isIndyError(error, 'AnoncredsMasterSecretDuplicateNameError')) { - // master secret id is the same as the master secret id passed in the create function - // so if it already exists we can just assign it. - this.logger.debug( - `Master secret with id '${masterSecretId}' already exists in wallet with handle '${walletHandle}'`, - { - indyError: 'AnoncredsMasterSecretDuplicateNameError', - } - ) - - return masterSecretId - } else { - if (!isIndyError(error)) { - throw new AriesFrameworkError('Attempted to throw Indy error, but it was not an Indy error') - } - - this.logger.error(`Error creating master secret with id ${masterSecretId}`, { - indyError: error.indyName, - error, - }) - - throw new WalletError( - `Error creating master secret with id ${masterSecretId} in wallet with handle '${walletHandle}'`, - { cause: error } - ) - } - } - } - - /** - * @deprecated The public did functionality of the wallet has been deprecated in favour of the DidsModule, which can be - * used to create and resolve dids. Currently the global agent public did functionality is still used by the `LedgerModule`, but - * will be removed once the `LedgerModule` has been deprecated. Do not use this property for new functionality, but rather - * use the `DidsModule`. - */ - public async initPublicDid(didConfig: DidConfig) { - // The Indy SDK cannot use a key to sign a request for the ledger. This is the only place where we need to call createDid - try { - const [did, verkey] = await this.indy.createAndStoreMyDid(this.handle, didConfig || {}) - - this.publicDidInfo = { - did, - verkey, - } - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - throw new WalletError('Error creating Did', { cause: error }) - } - } - - /** - * Create a key with an optional seed and keyType. - * The keypair is also automatically stored in the wallet afterwards - * - * Bls12381g1g2 and X25519 are not supported. - * - * @param seed string The seed for creating a key - * @param keyType KeyType the type of key that should be created - * - * @returns a Key instance with a publicKeyBase58 - * - * @throws {WalletError} When an unsupported keytype is requested - * @throws {WalletError} When the key could not be created - */ - public async createKey({ seed, keyType }: WalletCreateKeyOptions): Promise { - try { - // Ed25519 is supported natively in Indy wallet - if (keyType === KeyType.Ed25519) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - const verkey = await this.indy.createKey(this.handle, { seed, crypto_type: 'ed25519' }) - return Key.fromPublicKeyBase58(verkey, keyType) - } - - // Check if there is a signing key provider for the specified key type. - if (this.signingKeyProviderRegistry.hasProviderForKeyType(keyType)) { - const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(keyType) - - const keyPair = await signingKeyProvider.createKeyPair({ seed }) - await this.storeKeyPair(keyPair) - return Key.fromPublicKeyBase58(keyPair.publicKeyBase58, keyType) - } - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - throw new WalletError(`Error creating key with key type '${keyType}': ${error.message}`, { cause: error }) - } - - throw new WalletError(`Unsupported key type: '${keyType}' for wallet IndyWallet`) - } - - /** - * sign a Buffer with an instance of a Key class - * - * Bls12381g1g2, Bls12381g1 and X25519 are not supported. - * - * @param data Buffer The data that needs to be signed - * @param key Key The key that is used to sign the data - * - * @returns A signature for the data - */ - public async sign({ data, key }: WalletSignOptions): Promise { - try { - // Ed25519 is supported natively in Indy wallet - if (key.keyType === KeyType.Ed25519) { - // Checks to see if it is an not an Array of messages, but just a single one - if (!TypedArrayEncoder.isTypedArray(data)) { - throw new WalletError(`${KeyType.Ed25519} does not support multiple singing of multiple messages`) - } - return await this.indy.cryptoSign(this.handle, key.publicKeyBase58, data as Buffer) - } - - // Check if there is a signing key provider for the specified key type. - if (this.signingKeyProviderRegistry.hasProviderForKeyType(key.keyType)) { - const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(key.keyType) - - const keyPair = await this.retrieveKeyPair(key.publicKeyBase58) - const signed = await signingKeyProvider.sign({ - data, - privateKeyBase58: keyPair.privateKeyBase58, - publicKeyBase58: key.publicKeyBase58, - }) - - return signed - } - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - throw new WalletError(`Error signing data with verkey ${key.publicKeyBase58}`, { cause: error }) - } - throw new WalletError(`Unsupported keyType: ${key.keyType}`) - } - - /** - * Verify the signature with the data and the used key - * - * Bls12381g1g2, Bls12381g1 and X25519 are not supported. - * - * @param data Buffer The data that has to be confirmed to be signed - * @param key Key The key that was used in the signing process - * @param signature Buffer The signature that was created by the signing process - * - * @returns A boolean whether the signature was created with the supplied data and key - * - * @throws {WalletError} When it could not do the verification - * @throws {WalletError} When an unsupported keytype is used - */ - public async verify({ data, key, signature }: WalletVerifyOptions): Promise { - try { - // Ed25519 is supported natively in Indy wallet - if (key.keyType === KeyType.Ed25519) { - // Checks to see if it is an not an Array of messages, but just a single one - if (!TypedArrayEncoder.isTypedArray(data)) { - throw new WalletError(`${KeyType.Ed25519} does not support multiple singing of multiple messages`) - } - return await this.indy.cryptoVerify(key.publicKeyBase58, data as Buffer, signature) - } - - // Check if there is a signing key provider for the specified key type. - if (this.signingKeyProviderRegistry.hasProviderForKeyType(key.keyType)) { - const signingKeyProvider = this.signingKeyProviderRegistry.getProviderForKeyType(key.keyType) - - const signed = await signingKeyProvider.verify({ - data, - signature, - publicKeyBase58: key.publicKeyBase58, - }) - - return signed - } - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - throw new WalletError(`Error verifying signature of data signed with verkey ${key.publicKeyBase58}`, { - cause: error, - }) - } - throw new WalletError(`Unsupported keyType: ${key.keyType}`) - } - - public async pack( - payload: Record, - recipientKeys: string[], - senderVerkey?: string - ): Promise { - try { - const messageRaw = JsonEncoder.toBuffer(payload) - const packedMessage = await this.indy.packMessage(this.handle, messageRaw, recipientKeys, senderVerkey ?? null) - return JsonEncoder.fromBuffer(packedMessage) - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - throw new WalletError('Error packing message', { cause: error }) - } - } - - public async unpack(messagePackage: EncryptedMessage): Promise { - try { - const unpackedMessageBuffer = await this.indy.unpackMessage(this.handle, JsonEncoder.toBuffer(messagePackage)) - const unpackedMessage = JsonEncoder.fromBuffer(unpackedMessageBuffer) - return { - senderKey: unpackedMessage.sender_verkey, - recipientKey: unpackedMessage.recipient_verkey, - plaintextMessage: JsonEncoder.fromString(unpackedMessage.message), - } - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - throw new WalletError('Error unpacking message', { cause: error }) - } - } - - public async generateNonce(): Promise { - try { - return await this.indy.generateNonce() - } catch (error) { - if (!isError(error)) { - throw new AriesFrameworkError('Attempted to throw error, but it was not of type Error') - } - throw new WalletError('Error generating nonce', { cause: error }) - } - } - - private async retrieveKeyPair(publicKeyBase58: string): Promise { - try { - const { value } = await this.indy.getWalletRecord(this.handle, 'KeyPairRecord', `key-${publicKeyBase58}`, {}) - if (value) { - return JsonEncoder.fromString(value) as KeyPair - } else { - throw new WalletError(`No content found for record with public key: ${publicKeyBase58}`) - } - } catch (error) { - if (isIndyError(error, 'WalletItemNotFound')) { - throw new RecordNotFoundError(`KeyPairRecord not found for public key: ${publicKeyBase58}.`, { - recordType: 'KeyPairRecord', - cause: error, - }) - } - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - private async storeKeyPair(keyPair: KeyPair): Promise { - try { - await this.indy.addWalletRecord( - this.handle, - 'KeyPairRecord', - `key-${keyPair.publicKeyBase58}`, - JSON.stringify(keyPair), - { - keyType: keyPair.keyType, - } - ) - } catch (error) { - if (isIndyError(error, 'WalletItemAlreadyExists')) { - throw new RecordDuplicateError(`Record already exists`, { recordType: 'KeyPairRecord' }) - } - throw isIndyError(error) ? new IndySdkError(error) : error - } - } - - public async generateWalletKey() { - try { - return await this.indy.generateWalletKey() - } catch (error) { - throw new WalletError('Error generating wallet key', { cause: error }) - } - } -} diff --git a/packages/core/src/wallet/index.ts b/packages/core/src/wallet/index.ts index 6e19fc5d3c..e60dcfdb68 100644 --- a/packages/core/src/wallet/index.ts +++ b/packages/core/src/wallet/index.ts @@ -1,4 +1,3 @@ export * from './Wallet' -export * from './IndyWallet' export * from './WalletApi' export * from './WalletModule' diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index e8fd1f8a29..b5a07fee04 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -1,77 +1,52 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { Observable } from 'rxjs' import type { - AcceptCredentialOfferOptions, AgentDependencies, + BaseEvent, BasicMessage, BasicMessageStateChangedEvent, ConnectionRecordProps, - CredentialDefinitionTemplate, CredentialStateChangedEvent, InitConfig, InjectionToken, ProofStateChangedEvent, - SchemaTemplate, Wallet, } from '../src' import type { AgentModulesInput, EmptyModuleMap } from '../src/agent/AgentModules' import type { TrustPingReceivedEvent, TrustPingResponseReceivedEvent } from '../src/modules/connections/TrustPingEvents' -import type { IndyOfferCredentialFormat } from '../src/modules/credentials/formats/indy/IndyCredentialFormat' -import type { ProofAttributeInfo, ProofPredicateInfoOptions } from '../src/modules/proofs/formats/indy/models' -import type { AutoAcceptProof } from '../src/modules/proofs/models/ProofAutoAcceptType' -import type { Awaited, WalletConfig } from '../src/types' -import type { CredDef, Schema } from 'indy-sdk' -import type { Observable } from 'rxjs' +import type { WalletConfig } from '../src/types' import { readFileSync } from 'fs' import path from 'path' -import { firstValueFrom, ReplaySubject, Subject } from 'rxjs' +import { firstValueFrom, ReplaySubject } from 'rxjs' import { catchError, filter, map, timeout } from 'rxjs/operators' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { BbsModule } from '../../bbs-signatures/src/BbsModule' import { agentDependencies, WalletScheme } from '../../node/src' import { - CredentialsModule, - IndyCredentialFormatService, - JsonLdCredentialFormatService, - V1CredentialProtocol, - V2CredentialProtocol, - W3cVcModule, Agent, AgentConfig, AgentContext, - AriesFrameworkError, BasicMessageEventTypes, ConnectionRecord, CredentialEventTypes, CredentialState, - TrustPingEventTypes, DependencyManager, DidExchangeRole, DidExchangeState, HandshakeProtocol, InjectionSymbols, ProofEventTypes, + TrustPingEventTypes, } from '../src' import { Key, KeyType } from '../src/crypto' -import { Attachment, AttachmentData } from '../src/decorators/attachment/Attachment' -import { AutoAcceptCredential } from '../src/modules/credentials/models/CredentialAutoAcceptType' -import { V1CredentialPreview } from '../src/modules/credentials/protocol/v1/messages/V1CredentialPreview' import { DidCommV1Service } from '../src/modules/dids' import { DidKey } from '../src/modules/dids/methods/key' import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' import { OutOfBandInvitation } from '../src/modules/oob/messages' import { OutOfBandRecord } from '../src/modules/oob/repository' -import { PredicateType } from '../src/modules/proofs/formats/indy/models' import { ProofState } from '../src/modules/proofs/models/ProofState' -import { V1PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import { customDocumentLoader } from '../src/modules/vc/__tests__/documentLoader' import { KeyDerivationMethod } from '../src/types' -import { LinkedAttachment } from '../src/utils/LinkedAttachment' -import { uuid } from '../src/utils/uuid' import testLogger, { TestLogger } from './logger' @@ -82,8 +57,8 @@ export const genesisPath = process.env.GENESIS_TXN_PATH export const genesisTransactions = readFileSync(genesisPath).toString('utf-8') export const publicDidSeed = process.env.TEST_AGENT_PUBLIC_DID_SEED ?? '000000000000000000000000Trustee9' -const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${number}.${number}` | `${number}` -const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' +export const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${number}.${number}` | `${number}` +export const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' export { agentDependencies } export function getAgentOptions( @@ -100,16 +75,6 @@ export function getAgentOptions + e.type === ProofEventTypes.ProofStateChanged +const isCredentialStateChangedEvent = (e: BaseEvent): e is CredentialStateChangedEvent => + e.type === CredentialEventTypes.CredentialStateChanged +const isTrustPingReceivedEvent = (e: BaseEvent): e is TrustPingReceivedEvent => + e.type === TrustPingEventTypes.TrustPingReceivedEvent +const isTrustPingResponseReceivedEvent = (e: BaseEvent): e is TrustPingResponseReceivedEvent => + e.type === TrustPingEventTypes.TrustPingResponseReceivedEvent +const isBasicMessageStateChangedEvent = (e: BaseEvent): e is BasicMessageStateChangedEvent => + e.type === BasicMessageEventTypes.BasicMessageStateChanged + export function waitForProofExchangeRecordSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, parentThreadId, @@ -222,10 +190,10 @@ export function waitForProofExchangeRecordSubject( timeoutMs?: number } ) { - const observable: Observable = - subject instanceof ReplaySubject ? subject.asObservable() : subject + const observable: Observable = subject instanceof ReplaySubject ? subject.asObservable() : subject return firstValueFrom( observable.pipe( + filter(isProofStateChangedEvent), filter((e) => previousState === undefined || e.payload.previousState === previousState), filter((e) => threadId === undefined || e.payload.proofRecord.threadId === threadId), filter((e) => parentThreadId === undefined || e.payload.proofRecord.parentThreadId === parentThreadId), @@ -259,7 +227,7 @@ export async function waitForTrustPingReceivedEvent( } export function waitForTrustPingReceivedEventSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, timeoutMs = 10000, @@ -271,6 +239,7 @@ export function waitForTrustPingReceivedEventSubject( const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject return firstValueFrom( observable.pipe( + filter(isTrustPingReceivedEvent), filter((e) => threadId === undefined || e.payload.message.threadId === threadId), timeout(timeoutMs), catchError(() => { @@ -300,7 +269,7 @@ export async function waitForTrustPingResponseReceivedEvent( } export function waitForTrustPingResponseReceivedEventSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, timeoutMs = 10000, @@ -312,6 +281,7 @@ export function waitForTrustPingResponseReceivedEventSubject( const observable = subject instanceof ReplaySubject ? subject.asObservable() : subject return firstValueFrom( observable.pipe( + filter(isTrustPingResponseReceivedEvent), filter((e) => threadId === undefined || e.payload.message.threadId === threadId), timeout(timeoutMs), catchError(() => { @@ -327,7 +297,7 @@ export function waitForTrustPingResponseReceivedEventSubject( } export function waitForCredentialRecordSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, state, @@ -344,6 +314,7 @@ export function waitForCredentialRecordSubject( return firstValueFrom( observable.pipe( + filter(isCredentialStateChangedEvent), filter((e) => previousState === undefined || e.payload.previousState === previousState), filter((e) => threadId === undefined || e.payload.credentialRecord.threadId === threadId), filter((e) => state === undefined || e.payload.credentialRecord.state === state), @@ -478,129 +449,6 @@ export async function makeConnection(agentA: Agent, agentB: Agent) { return [agentAConnection, agentBConnection] } -export async function registerSchema(agent: Agent, schemaTemplate: SchemaTemplate): Promise { - const schema = await agent.ledger.registerSchema(schemaTemplate) - testLogger.test(`created schema with id ${schema.id}`, schema) - return schema -} - -export async function registerDefinition( - agent: Agent, - definitionTemplate: CredentialDefinitionTemplate -): Promise { - const credentialDefinition = await agent.ledger.registerCredentialDefinition(definitionTemplate) - testLogger.test(`created credential definition with id ${credentialDefinition.id}`, credentialDefinition) - return credentialDefinition -} - -export async function prepareForIssuance(agent: Agent, attributes: string[]) { - const publicDid = agent.publicDid?.did - - if (!publicDid) { - throw new AriesFrameworkError('No public did') - } - - await ensurePublicDidIsOnLedger(agent, publicDid) - - const schema = await registerSchema(agent, { - attributes, - name: `schema-${uuid()}`, - version: '1.0', - }) - - const definition = await registerDefinition(agent, { - schema, - signatureType: 'CL', - supportRevocation: false, - tag: 'default', - }) - - return { - schema, - definition, - publicDid, - } -} - -export async function ensurePublicDidIsOnLedger(agent: Agent, publicDid: string) { - try { - testLogger.test(`Ensure test DID ${publicDid} is written to ledger`) - await agent.ledger.getPublicDid(publicDid) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } catch (error: any) { - // Unfortunately, this won't prevent from the test suite running because of Jest runner runs all tests - // regardless of thrown errors. We're more explicit about the problem with this error handling. - throw new Error(`Test DID ${publicDid} is not written on ledger or ledger is not available: ${error.message}`) - } -} - -/** - * Assumes that the autoAcceptCredential is set to {@link AutoAcceptCredential.ContentApproved} - */ -export async function issueCredential({ - issuerAgent, - issuerConnectionId, - holderAgent, - credentialTemplate, -}: { - issuerAgent: Agent - issuerConnectionId: string - holderAgent: Agent - credentialTemplate: IndyOfferCredentialFormat -}) { - const issuerReplay = new ReplaySubject() - const holderReplay = new ReplaySubject() - - issuerAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(issuerReplay) - holderAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(holderReplay) - - let issuerCredentialRecord = await issuerAgent.credentials.offerCredential({ - comment: 'some comment about credential', - connectionId: issuerConnectionId, - protocolVersion: 'v1', - credentialFormats: { - indy: { - attributes: credentialTemplate.attributes, - credentialDefinitionId: credentialTemplate.credentialDefinitionId, - linkedAttachments: credentialTemplate.linkedAttachments, - }, - }, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - }) - - let holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.OfferReceived, - }) - - const acceptOfferOptions: AcceptCredentialOfferOptions = { - credentialRecordId: holderCredentialRecord.id, - autoAcceptCredential: AutoAcceptCredential.ContentApproved, - } - - await holderAgent.credentials.acceptOffer(acceptOfferOptions) - - // Because we use auto-accept it can take a while to have the whole credential flow finished - // Both parties need to interact with the ledger and sign/verify the credential - holderCredentialRecord = await waitForCredentialRecordSubject(holderReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.Done, - }) - issuerCredentialRecord = await waitForCredentialRecordSubject(issuerReplay, { - threadId: issuerCredentialRecord.threadId, - state: CredentialState.Done, - }) - - return { - issuerCredential: issuerCredentialRecord, - holderCredential: holderCredentialRecord, - } -} - /** * Returns mock of function with correct type annotations according to original function `fn`. * It can be used also for class methods. @@ -620,266 +468,3 @@ export function mockFunction any>(fn: T): jest.Moc export function mockProperty(object: T, property: K, value: T[K]) { Object.defineProperty(object, property, { get: () => value }) } - -export async function presentProof({ - verifierAgent, - verifierConnectionId, - holderAgent, - presentationTemplate: { attributes, predicates }, -}: { - verifierAgent: Agent - verifierConnectionId: string - holderAgent: Agent - presentationTemplate: { - attributes?: Record - predicates?: Record - } -}) { - const verifierReplay = new ReplaySubject() - const holderReplay = new ReplaySubject() - - verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) - holderAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(holderReplay) - - let holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { - state: ProofState.RequestReceived, - }) - - let verifierRecord = await verifierAgent.proofs.requestProof({ - connectionId: verifierConnectionId, - proofFormats: { - indy: { - name: 'test-proof-request', - requestedAttributes: attributes, - requestedPredicates: predicates, - version: '1.0', - }, - }, - protocolVersion: 'v2', - }) - - let holderRecord = await holderProofExchangeRecordPromise - - const requestedCredentials = await holderAgent.proofs.selectCredentialsForRequest({ - proofRecordId: holderRecord.id, - }) - - const verifierProofExchangeRecordPromise = waitForProofExchangeRecordSubject(verifierReplay, { - threadId: holderRecord.threadId, - state: ProofState.PresentationReceived, - }) - - await holderAgent.proofs.acceptRequest({ - proofRecordId: holderRecord.id, - proofFormats: { indy: requestedCredentials.proofFormats.indy }, - }) - - verifierRecord = await verifierProofExchangeRecordPromise - - // assert presentation is valid - expect(verifierRecord.isVerified).toBe(true) - - holderProofExchangeRecordPromise = waitForProofExchangeRecordSubject(holderReplay, { - threadId: holderRecord.threadId, - state: ProofState.Done, - }) - - verifierRecord = await verifierAgent.proofs.acceptPresentation({ proofRecordId: verifierRecord.id }) - holderRecord = await holderProofExchangeRecordPromise - - return { - verifierProof: verifierRecord, - holderProof: holderRecord, - } -} - -// Helper type to get the type of the agents (with the custom modules) for the credential tests -export type CredentialTestsAgent = Awaited>['aliceAgent'] -export async function setupCredentialTests( - faberName: string, - aliceName: string, - autoAcceptCredentials?: AutoAcceptCredential -) { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - - const indyCredentialFormat = new IndyCredentialFormatService() - const jsonLdCredentialFormat = new JsonLdCredentialFormatService() - - // TODO remove the dependency on BbsModule - const modules = { - bbs: new BbsModule(), - - // Initialize custom credentials module (with jsonLdCredentialFormat enabled) - credentials: new CredentialsModule({ - autoAcceptCredentials, - credentialProtocols: [ - new V1CredentialProtocol({ indyCredentialFormat }), - new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormat, jsonLdCredentialFormat], - }), - ], - }), - // Register custom w3cVc module so we can define the test document loader - w3cVc: new W3cVcModule({ - documentLoader: customDocumentLoader, - }), - } - const faberAgentOptions = getAgentOptions( - faberName, - { - endpoints: ['rxjs:faber'], - }, - modules - ) - - const aliceAgentOptions = getAgentOptions( - aliceName, - { - endpoints: ['rxjs:alice'], - }, - modules - ) - const faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - - const aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() - - const { - schema, - definition: { id: credDefId }, - } = await prepareForIssuance(faberAgent, ['name', 'age', 'profile_picture', 'x-ray']) - - const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) - - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(faberReplay) - aliceAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(aliceReplay) - - return { faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection, faberReplay, aliceReplay } -} - -export async function setupProofsTest(faberName: string, aliceName: string, autoAcceptProofs?: AutoAcceptProof) { - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - }) - - const unique = uuid().substring(0, 4) - - const faberAgentOptions = getAgentOptions(`${faberName} - ${unique}`, { - autoAcceptProofs, - endpoints: ['rxjs:faber'], - }) - - const aliceAgentOptions = getAgentOptions(`${aliceName} - ${unique}`, { - autoAcceptProofs, - endpoints: ['rxjs:alice'], - }) - - const faberMessages = new Subject() - const aliceMessages = new Subject() - - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - const faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - - const aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() - - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) - - const [agentAConnection, agentBConnection] = await makeConnection(faberAgent, aliceAgent) - expect(agentAConnection.isReady).toBe(true) - expect(agentBConnection.isReady).toBe(true) - - const faberConnection = agentAConnection - const aliceConnection = agentBConnection - - const presentationPreview = new V1PresentationPreview({ - attributes: [ - { - name: 'name', - credentialDefinitionId: definition.id, - referent: '0', - value: 'John', - }, - { - name: 'image_0', - credentialDefinitionId: definition.id, - }, - ], - predicates: [ - { - name: 'age', - credentialDefinitionId: definition.id, - predicate: PredicateType.GreaterThanOrEqualTo, - threshold: 50, - }, - ], - }) - - await issueCredential({ - issuerAgent: faberAgent, - issuerConnectionId: faberConnection.id, - holderAgent: aliceAgent, - credentialTemplate: { - credentialDefinitionId: definition.id, - attributes: credentialPreview.attributes, - linkedAttachments: [ - new LinkedAttachment({ - name: 'image_0', - attachment: new Attachment({ - filename: 'picture-of-a-cat.png', - data: new AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), - }), - }), - new LinkedAttachment({ - name: 'image_1', - attachment: new Attachment({ - filename: 'picture-of-a-dog.png', - data: new AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), - }), - }), - ], - }, - }) - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(faberReplay) - aliceAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay) - - return { - faberAgent, - aliceAgent, - credDefId: definition.id, - faberConnection, - aliceConnection, - presentationPreview, - faberReplay, - aliceReplay, - } -} diff --git a/packages/core/tests/ledger.test.ts b/packages/core/tests/ledger.test.ts deleted file mode 100644 index 9d3411e54d..0000000000 --- a/packages/core/tests/ledger.test.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { promises } from 'fs' -import * as indy from 'indy-sdk' - -import { KeyType } from '../src' -import { Agent } from '../src/agent/Agent' -import { - DID_IDENTIFIER_REGEX, - indyDidFromPublicKeyBase58, - isAbbreviatedVerkey, - isFullVerkey, - VERKEY_REGEX, -} from '../src/utils/did' -import { sleep } from '../src/utils/sleep' - -import { genesisPath, getAgentOptions } from './helpers' -import testLogger from './logger' - -describe('ledger', () => { - let faberAgent: Agent - let schemaId: indy.SchemaId - - beforeAll(async () => { - faberAgent = new Agent(getAgentOptions('Faber Ledger')) - await faberAgent.initialize() - }) - - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - }) - - test(`initialization of agent's public DID`, async () => { - const publicDid = faberAgent.publicDid - testLogger.test('faberAgentPublicDid', publicDid) - - expect(publicDid).toEqual( - expect.objectContaining({ - did: expect.stringMatching(DID_IDENTIFIER_REGEX), - verkey: expect.stringMatching(VERKEY_REGEX), - }) - ) - }) - - test('get public DID from ledger', async () => { - if (!faberAgent.publicDid) { - throw new Error('Agent does not have public did.') - } - - const result = await faberAgent.ledger.getPublicDid(faberAgent.publicDid.did) - - let { verkey } = faberAgent.publicDid - // Agent’s public did stored locally in Indy wallet and created from public did seed during - // its initialization always returns full verkey. Therefore we need to align that here. - if (isFullVerkey(verkey) && isAbbreviatedVerkey(result.verkey)) { - verkey = await indy.abbreviateVerkey(faberAgent.publicDid.did, verkey) - } - - expect(result).toEqual( - expect.objectContaining({ - did: faberAgent.publicDid.did, - verkey: verkey, - role: '0', - }) - ) - }) - - test('register public DID on ledger', async () => { - if (!faberAgent.publicDid) { - throw new Error('Agent does not have public did.') - } - - const faberWallet = faberAgent.context.wallet - const key = await faberWallet.createKey({ keyType: KeyType.Ed25519 }) - const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) - - const result = await faberAgent.ledger.registerPublicDid(did, key.publicKeyBase58, 'alias', 'TRUST_ANCHOR') - - expect(result).toEqual(did) - }) - - test('register schema on ledger', async () => { - if (!faberAgent.publicDid) { - throw new Error('Agent does not have public did.') - } - - const schemaName = `test-schema-${Date.now()}` - const schemaTemplate = { - name: schemaName, - attributes: ['name', 'age'], - version: '1.0', - } - - const schema = await faberAgent.ledger.registerSchema(schemaTemplate) - schemaId = schema.id - - await sleep(2000) - - const ledgerSchema = await faberAgent.ledger.getSchema(schemaId) - - expect(schemaId).toBe(`${faberAgent.publicDid.did}:2:${schemaName}:1.0`) - - expect(ledgerSchema).toEqual( - expect.objectContaining({ - attrNames: expect.arrayContaining(schemaTemplate.attributes), - id: `${faberAgent.publicDid.did}:2:${schemaName}:1.0`, - name: schemaName, - seqNo: schema.seqNo, - ver: schemaTemplate.version, - version: schemaTemplate.version, - }) - ) - }) - - test('register definition on ledger', async () => { - if (!faberAgent.publicDid) { - throw new Error('Agent does not have public did.') - } - const schema = await faberAgent.ledger.getSchema(schemaId) - const credentialDefinitionTemplate = { - schema: schema, - tag: 'TAG', - signatureType: 'CL' as const, - supportRevocation: true, - } - - const credentialDefinition = await faberAgent.ledger.registerCredentialDefinition(credentialDefinitionTemplate) - - await sleep(2000) - - const ledgerCredDef = await faberAgent.ledger.getCredentialDefinition(credentialDefinition.id) - - const credDefIdRegExp = new RegExp(`${faberAgent.publicDid.did}:3:CL:[0-9]+:TAG`) - expect(ledgerCredDef).toEqual( - expect.objectContaining({ - id: expect.stringMatching(credDefIdRegExp), - schemaId: String(schema.seqNo), - type: credentialDefinitionTemplate.signatureType, - tag: credentialDefinitionTemplate.tag, - ver: '1.0', - value: expect.objectContaining({ - primary: expect.anything(), - revocation: expect.anything(), - }), - }) - ) - }) - - it('should correctly store the genesis file if genesis transactions is passed', async () => { - const genesisTransactions = await promises.readFile(genesisPath, { encoding: 'utf-8' }) - const agentOptions = getAgentOptions('Faber Ledger Genesis Transactions', { - indyLedgers: [ - { - id: 'pool-Faber Ledger Genesis Transactions', - indyNamespace: 'pool-faber-ledger-genesis-transactions', - isProduction: false, - genesisTransactions, - }, - ], - }) - const agent = new Agent(agentOptions) - await agent.initialize() - - if (!faberAgent.publicDid?.did) { - throw new Error('No public did') - } - - const did = await agent.ledger.getPublicDid(faberAgent.publicDid.did) - expect(did.did).toEqual(faberAgent.publicDid.did) - }) -}) diff --git a/packages/core/tests/migration.test.ts b/packages/core/tests/migration.test.ts index 0dbf6b02dc..120c24e030 100644 --- a/packages/core/tests/migration.test.ts +++ b/packages/core/tests/migration.test.ts @@ -5,7 +5,7 @@ import { UpdateAssistant } from '../src/storage/migration/UpdateAssistant' import { getAgentOptions } from './helpers' -const agentOptions = getAgentOptions('Migration', { publicDidSeed: undefined, indyLedgers: [] }) +const agentOptions = getAgentOptions('Migration', { publicDidSeed: undefined }) describe('migration', () => { test('manually initiating the update assistant to perform an update', async () => { diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 94161fd838..f70e283221 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -28,7 +28,7 @@ import { DidCommMessageRepository, DidCommMessageRole } from '../src/storage' import { JsonEncoder } from '../src/utils' import { TestMessage } from './TestMessage' -import { getAgentOptions, prepareForIssuance, waitForCredentialRecord } from './helpers' +import { getAgentOptions, prepareForIndyIssuance, waitForCredentialRecord } from './helpers' const faberAgentOptions = getAgentOptions('Faber Agent OOB', { endpoints: ['rxjs:faber'], @@ -79,7 +79,7 @@ describe('out of band', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIssuance(faberAgent, ['name', 'age', 'profile_picture', 'x-ray']) + const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age', 'profile_picture', 'x-ray']) credentialTemplate = { protocolVersion: 'v1', diff --git a/packages/indy-sdk/src/IndySdkModule.ts b/packages/indy-sdk/src/IndySdkModule.ts index 20574f3d46..2d52238b7d 100644 --- a/packages/indy-sdk/src/IndySdkModule.ts +++ b/packages/indy-sdk/src/IndySdkModule.ts @@ -1,5 +1,5 @@ import type { IndySdkModuleConfigOptions } from './IndySdkModuleConfig' -import type { DependencyManager, Module } from '@aries-framework/core' +import type { AgentContext, DependencyManager, Module } from '@aries-framework/core' import { AnonCredsHolderServiceSymbol, @@ -10,6 +10,7 @@ import { InjectionSymbols } from '@aries-framework/core' import { IndySdkModuleConfig } from './IndySdkModuleConfig' import { IndySdkHolderService, IndySdkIssuerService, IndySdkVerifierService } from './anoncreds' +import { IndySdkPoolService } from './ledger' import { IndySdkStorageService } from './storage' import { IndySdkSymbol } from './types' import { IndySdkWallet } from './wallet' @@ -32,4 +33,14 @@ export class IndySdkModule implements Module { dependencyManager.registerSingleton(AnonCredsHolderServiceSymbol, IndySdkHolderService) dependencyManager.registerSingleton(AnonCredsVerifierServiceSymbol, IndySdkVerifierService) } + + public async initialize(agentContext: AgentContext): Promise { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + for (const pool of indySdkPoolService.pools) { + if (pool.config.connectOnStartup) { + await pool.connect() + } + } + } } diff --git a/packages/indy-sdk/src/IndySdkModuleConfig.ts b/packages/indy-sdk/src/IndySdkModuleConfig.ts index e5b16142ee..3a269a93f5 100644 --- a/packages/indy-sdk/src/IndySdkModuleConfig.ts +++ b/packages/indy-sdk/src/IndySdkModuleConfig.ts @@ -1,3 +1,4 @@ +import type { IndySdkPoolConfig } from './ledger' import type * as IndySdk from 'indy-sdk' /** @@ -29,6 +30,26 @@ export interface IndySdkModuleConfigOptions { * ``` */ indySdk: typeof IndySdk + + /** + * Array of indy networks to connect to. Each item in the list must include either the `genesisPath` or `genesisTransactions` property. + * + * @default [] + * + * @example + * ``` + * { + * isProduction: false, + * genesisPath: '/path/to/genesis.txn', + * indyNamespace: 'localhost:test', + * transactionAuthorAgreement: { + * version: '1', + * acceptanceMechanism: 'accept' + * } + * } + * ``` + */ + networks?: IndySdkPoolConfig[] } export class IndySdkModuleConfig { @@ -42,4 +63,8 @@ export class IndySdkModuleConfig { public get indySdk() { return this.options.indySdk } + + public get networks() { + return this.options.networks ?? [] + } } diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index 7ddb4a5db5..5c25805cbd 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -430,7 +430,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( - `Using ledger '${pool.id}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` + `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` ) // TODO: implement caching for returned deltas diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts index 8553af9295..11ed6bc22d 100644 --- a/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts +++ b/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts @@ -1,5 +1,6 @@ import type { IndyEndpointAttrib } from './didSovUtil' import type { IndySdkPool } from '../ledger' +import type { IndySdk } from '../types' import type { AgentContext, DidRegistrar, @@ -11,34 +12,24 @@ import type { } from '@aries-framework/core' import type { NymRole } from 'indy-sdk' -import { inject, injectable, DidDocumentRole, DidRecord, DidRepository } from '@aries-framework/core' +import { DidDocumentRole, DidRecord, DidRepository } from '@aries-framework/core' import { IndySdkError } from '../error' import { isIndyError } from '../error/indyError' import { IndySdkPoolService } from '../ledger' -import { IndySdk, IndySdkSymbol } from '../types' +import { IndySdkSymbol } from '../types' import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovUtil' -@injectable() export class IndySdkSovDidRegistrar implements DidRegistrar { public readonly supportedMethods = ['sov'] - private didRepository: DidRepository - private indySdk: IndySdk - private indySdkPoolService: IndySdkPoolService - - public constructor( - didRepository: DidRepository, - indySdkPoolService: IndySdkPoolService, - @inject(IndySdkSymbol) indySdk: IndySdk - ) { - this.didRepository = didRepository - this.indySdk = indySdk - this.indySdkPoolService = indySdkPoolService - } public async create(agentContext: AgentContext, options: IndySdkSovDidCreateOptions): Promise { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + const { alias, role, submitterDid, indyNamespace } = options.options const seed = options.secret?.seed @@ -70,7 +61,7 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { // WalletItemNotFound when it needs to sign ledger transactions using this did. This means we need // to rely directly on the indy SDK, as we don't want to expose a createDid method just for. assertIndySdkWallet(agentContext.wallet) - const [unqualifiedIndyDid, verkey] = await this.indySdk.createAndStoreMyDid(agentContext.wallet.handle, { + const [unqualifiedIndyDid, verkey] = await indySdk.createAndStoreMyDid(agentContext.wallet.handle, { seed, }) @@ -79,7 +70,7 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { // TODO: it should be possible to pass the pool used for writing to the indy ledger service. // The easiest way to do this would be to make the submitterDid a fully qualified did, including the indy namespace. - const pool = this.indySdkPoolService.getPoolForNamespace(indyNamespace) + const pool = indySdkPoolService.getPoolForNamespace(indyNamespace) await this.registerPublicDid(agentContext, unqualifiedSubmitterDid, unqualifiedIndyDid, verkey, alias, pool, role) // Create did document @@ -112,7 +103,7 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { qualifiedIndyDid, }, }) - await this.didRepository.save(agentContext, didRecord) + await didRepository.save(agentContext, didRecord) return { didDocumentMetadata: { @@ -178,12 +169,15 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { pool: IndySdkPool, role?: NymRole ) { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + try { agentContext.config.logger.debug(`Register public did '${targetDid}' on ledger '${pool.didIndyNamespace}'`) - const request = await this.indySdk.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) + const request = await indySdk.buildNymRequest(submitterDid, targetDid, verkey, alias, role || null) - const response = await this.indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterDid) agentContext.config.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.didIndyNamespace}'`, { response, @@ -214,12 +208,15 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { endpoints: IndyEndpointAttrib, pool: IndySdkPool ): Promise { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + try { agentContext.config.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.didIndyNamespace}'`, endpoints) - const request = await this.indySdk.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) + const request = await indySdk.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) - const response = await this.indySdkPoolService.submitWriteRequest(agentContext, pool, request, did) + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, did) agentContext.config.logger.debug( `Successfully set endpoints for did '${did}' on ledger '${pool.didIndyNamespace}'`, { diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts index c4d584568c..98007e5166 100644 --- a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts @@ -1,24 +1,14 @@ import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndySdk } from '../types' import type { DidResolutionResult, ParsedDid, DidResolver, AgentContext } from '@aries-framework/core' -import { inject, injectable } from '@aries-framework/core' - import { isIndyError, IndySdkError } from '../error' import { IndySdkPoolService } from '../ledger/IndySdkPoolService' -import { IndySdkSymbol, IndySdk } from '../types' +import { IndySdkSymbol } from '../types' import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovUtil' -@injectable() export class IndySdkSovDidResolver implements DidResolver { - private indySdk: IndySdk - private indySdkPoolService: IndySdkPoolService - - public constructor(indyPoolService: IndySdkPoolService, @inject(IndySdkSymbol) indySdk: IndySdk) { - this.indySdk = indySdk - this.indySdkPoolService = indyPoolService - } - public readonly supportedMethods = ['sov'] public async resolve(agentContext: AgentContext, did: string, parsed: ParsedDid): Promise { @@ -50,24 +40,29 @@ export class IndySdkSovDidResolver implements DidResolver { } private async getPublicDid(agentContext: AgentContext, did: string) { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + // Getting the pool for a did also retrieves the DID. We can just use that - const { did: didResponse } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + const { did: didResponse } = await indySdkPoolService.getPoolForDid(agentContext, did) return didResponse } private async getEndpointsForDid(agentContext: AgentContext, did: string) { - const { pool } = await this.indySdkPoolService.getPoolForDid(agentContext, did) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) try { agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.didIndyNamespace}'`) - const request = await this.indySdk.buildGetAttribRequest(null, did, 'endpoint', null, null) + const request = await indySdk.buildGetAttribRequest(null, did, 'endpoint', null, null) agentContext.config.logger.debug( `Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.didIndyNamespace}'` ) - const response = await this.indySdkPoolService.submitReadRequest(pool, request) + const response = await indySdkPoolService.submitReadRequest(pool, request) if (!response.result.data) return {} diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts new file mode 100644 index 0000000000..2a287c1fac --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts @@ -0,0 +1,375 @@ +import type { IndySdkPool } from '../../ledger/IndySdkPool' +import type { Wallet, DidRecord, RecordSavedEvent } from '@aries-framework/core' + +import { + DidRepository, + IndyPoolService, + SigningProviderRegistry, + JsonTransformer, + DidDocumentRole, + EventEmitter, + RepositoryEventTypes, +} from '@aries-framework/core' +import { mockFunction, getAgentConfig, getAgentContext, agentDependencies } from '@aries-framework/core/tests/helpers' +import indySdk from 'indy-sdk' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' +import { IndySdkSymbol } from '../../types' +import { IndySdkWallet } from '../../wallet' +import { IndySdkSovDidRegistrar } from '../IndySdkSovDidRegistrar' + +jest.mock('../../ledger/IndySdkPoolService') +const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock +const indyPoolServiceMock = new IndySdkPoolServiceMock() + +mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { id: 'pool1', indyNamespace: 'pool1' }, +} as IndySdkPool) + +const agentConfig = getAgentConfig('IndySdkSovDidRegistrar') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) +const storageService = new InMemoryStorageService() +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const didRepository = new DidRepository(storageService, eventEmitter) + +const agentContext = getAgentContext({ + wallet, + registerInstances: [ + [DidRepository, didRepository], + [IndyPoolService, indyPoolServiceMock], + [IndySdkSymbol, indySdk], + ], + agentConfig, +}) + +const indySdkSovDidRegistrar = new IndySdkSovDidRegistrar() + +describe('IndySdkSovDidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + it('should return an error state if an invalid seed is provided', async () => { + const result = await indySdkSovDidRegistrar.create(agentContext, { + method: 'sov', + + options: { + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + seed: 'invalid', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid seed provided', + }, + }) + }) + + it('should return an error state if the wallet is not an indy wallet', async () => { + const agentContext = getAgentContext({ + wallet: {} as unknown as Wallet, + agentConfig, + }) + + const result = await indySdkSovDidRegistrar.create(agentContext, { + method: 'sov', + + options: { + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + seed: '12345678901234567890123456789012', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: Expected wallet to be instance of IndyWallet, found Object', + }, + }) + }) + + it('should return an error state if the submitter did is not qualified with did:sov', async () => { + const result = await indySdkSovDidRegistrar.create(agentContext, { + method: 'sov', + options: { + submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Submitter did must be a valid did:sov did', + }, + }) + }) + + it('should correctly create a did:sov document without services', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + + const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) + + const result = await indySdkSovDidRegistrar.create(agentContext, { + method: 'sov', + options: { + alias: 'Hello', + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: { + seed, + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + // Alias + 'Hello', + // Pool + { config: { id: 'pool1', indyNamespace: 'pool1' } }, + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: { + qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + }, + didRegistrationMetadata: { + didIndyNamespace: 'pool1', + }, + didState: { + state: 'finished', + did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], + assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], + keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + }, + secret: { + seed, + }, + }, + }) + }) + + it('should correctly create a did:sov document with services', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + + const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) + + const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const result = await indySdkSovDidRegistrar.create(agentContext, { + method: 'sov', + options: { + alias: 'Hello', + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['DIDComm', 'did-communication', 'endpoint'], + }, + }, + secret: { + seed, + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + // Alias + 'Hello', + // Pool + { config: { id: 'pool1', indyNamespace: 'pool1' } }, + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: { + qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + }, + didRegistrationMetadata: { + didIndyNamespace: 'pool1', + }, + didState: { + state: 'finished', + did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', + type: 'Ed25519VerificationKey2018', + controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], + assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], + keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + }, + secret: { + seed, + }, + }, + }) + }) + + it('should store the did document', async () => { + const seed = '96213c3d7fc8d4d6754c712fd969598e' + + const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('did')) + + const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const saveCalled = jest.fn() + eventEmitter.on>(RepositoryEventTypes.RecordSaved, saveCalled) + + await indySdkSovDidRegistrar.create(agentContext, { + method: 'sov', + options: { + alias: 'Hello', + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['DIDComm', 'did-communication', 'endpoint'], + }, + }, + secret: { + seed, + }, + }) + + expect(saveCalled).toHaveBeenCalledTimes(1) + const [, didRecord] = saveCalled.mock.calls[0] + + expect(didRecord).toMatchObject({ + did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], + qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + }, + didDocument: undefined, + }) + }) + + it('should return an error state when calling update', async () => { + const result = await indySdkSovDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:sov not implemented yet`, + }, + }) + }) + + it('should return an error state when calling deactivate', async () => { + const result = await indySdkSovDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:sov not implemented yet`, + }, + }) + }) +}) diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts new file mode 100644 index 0000000000..0f67c0bf76 --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts @@ -0,0 +1,128 @@ +import type { IndySdkPool } from '../../ledger' +import type { IndyEndpointAttrib } from '../didSovUtil' +import type { GetNymResponse } from 'indy-sdk' + +import { IndyPoolService, SigningProviderRegistry, JsonTransformer } from '@aries-framework/core' +import { parseDid } from '@aries-framework/core/src/modules/dids/domain/parse' +import { mockFunction, getAgentConfig, getAgentContext } from '@aries-framework/core/tests/helpers' +import indySdk from 'indy-sdk' + +import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' +import { IndySdkSymbol } from '../../types' +import { IndySdkWallet } from '../../wallet' +import { IndySdkSovDidResolver } from '../IndySdkSovDidResolver' + +import didSovR1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json' +import didSovWJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json' + +jest.mock('../../ledger/IndySdkPoolService') +const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock +const indyPoolServiceMock = new IndySdkPoolServiceMock() + +mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { id: 'pool1', indyNamespace: 'pool1' }, +} as IndySdkPool) + +const agentConfig = getAgentConfig('IndySdkSovDidResolver') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) + +const agentContext = getAgentContext({ + wallet, + agentConfig, + registerInstances: [ + [IndyPoolService, indyPoolServiceMock], + [IndySdkSymbol, indySdk], + ], +}) + +const indySdkSovDidResolver = new IndySdkSovDidResolver() + +describe('IndySdkSovDidResolver', () => { + it('should correctly resolve a did:sov document', async () => { + const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' + + const nymResponse: GetNymResponse = { + did: 'R1xKJw17sUoXhejEpugMYJ', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://ssi.com', + profile: 'https://profile.com', + hub: 'https://hub.com', + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didSovR1xKJw17sUoXhejEpugMYJFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should resolve a did:sov document with routingKeys and types entries in the attrib', async () => { + const did = 'did:sov:WJz9mHyW9BZksioQnRsrAo' + + const nymResponse: GetNymResponse = { + did: 'WJz9mHyW9BZksioQnRsrAo', + verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://agent.com', + types: ['endpoint', 'did-communication', 'DIDComm'], + routingKeys: ['routingKey1', 'routingKey2'], + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didSovWJz9mHyW9BZksioQnRsrAoFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { + const did = 'did:sov:R1xKJw17sUoXhejEpugMYJ' + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockRejectedValue(new Error('Error retrieving did')) + + const result = await indySdkSovDidResolver.resolve(agentContext, did, parseDid(did)) + + expect(result).toMatchObject({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:sov:R1xKJw17sUoXhejEpugMYJ': Error: Error retrieving did`, + }, + }) + }) +}) diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json similarity index 100% rename from packages/core/src/modules/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json rename to packages/indy-sdk/src/dids/__tests__/__fixtures__/didSovR1xKJw17sUoXhejEpugMYJ.json diff --git a/packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json similarity index 100% rename from packages/core/src/modules/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json rename to packages/indy-sdk/src/dids/__tests__/__fixtures__/didSovWJz9mHyW9BZksioQnRsrAo.json diff --git a/packages/indy-sdk/src/ledger/IndySdkPool.ts b/packages/indy-sdk/src/ledger/IndySdkPool.ts index a24a1c7ba5..cb19dd3052 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPool.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPool.ts @@ -18,10 +18,10 @@ export interface TransactionAuthorAgreement { export interface IndySdkPoolConfig { genesisPath?: string genesisTransactions?: string - id: string isProduction: boolean indyNamespace: string transactionAuthorAgreement?: TransactionAuthorAgreement + connectOnStartup?: boolean } export class IndySdkPool { @@ -57,10 +57,6 @@ export class IndySdkPool { return this.didIndyNamespace } - public get id() { - return this.poolConfig.id - } - public get config() { return this.poolConfig } @@ -84,7 +80,7 @@ export class IndySdkPool { await this.close() } - await this.indySdk.deletePoolLedgerConfig(this.poolConfig.id) + await this.indySdk.deletePoolLedgerConfig(this.poolConfig.indyNamespace) } public async connect() { @@ -103,7 +99,7 @@ export class IndySdkPool { } private async connectToLedger() { - const poolName = this.poolConfig.id + const poolName = this.poolConfig.indyNamespace const genesisPath = await this.getGenesisPath() if (!genesisPath) { @@ -142,7 +138,9 @@ export class IndySdkPool { const response = await this.submitRequest(request) if (isLedgerRejectResponse(response) || isLedgerReqnackResponse(response)) { - throw new IndySdkPoolError(`Ledger '${this.id}' rejected read transaction request: ${response.reason}`) + throw new IndySdkPoolError( + `Ledger '${this.didIndyNamespace}' rejected read transaction request: ${response.reason}` + ) } return response as LedgerReadReplyResponse @@ -152,7 +150,9 @@ export class IndySdkPool { const response = await this.submitRequest(request) if (isLedgerRejectResponse(response) || isLedgerReqnackResponse(response)) { - throw new IndySdkPoolError(`Ledger '${this.id}' rejected write transaction request: ${response.reason}`) + throw new IndySdkPoolError( + `Ledger '${this.didIndyNamespace}' rejected write transaction request: ${response.reason}` + ) } return response as LedgerWriteReplyResponse @@ -180,7 +180,7 @@ export class IndySdkPool { if (this.poolConfig.genesisPath) return this.poolConfig.genesisPath // Determine the genesisPath - const genesisPath = this.fileSystem.basePath + `/afj/genesis-${this.poolConfig.id}.txn` + const genesisPath = this.fileSystem.basePath + `/afj/genesis-${this.poolConfig.indyNamespace}.txn` // Store genesis data if provided if (this.poolConfig.genesisTransactions) { await this.fileSystem.write(genesisPath, this.poolConfig.genesisTransactions) diff --git a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts index 9d237fd336..f2d9a63c46 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts @@ -1,12 +1,13 @@ -import type { AcceptanceMechanisms, AuthorAgreement, IndySdkPoolConfig } from './IndySdkPool' +import type { AcceptanceMechanisms, AuthorAgreement } from './IndySdkPool' import type { AgentContext } from '@aries-framework/core' import type { GetNymResponse, LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' import { CacheModuleConfig, InjectionSymbols, Logger, injectable, inject, FileSystem } from '@aries-framework/core' import { Subject } from 'rxjs' +import { IndySdkModuleConfig } from '../IndySdkModuleConfig' import { IndySdkError, isIndyError } from '../error' -import { IndySdk } from '../types' +import { IndySdk, IndySdkSymbol } from '../types' import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' import { isSelfCertifiedDid } from '../utils/did' import { allSettled, onlyFulfilled, onlyRejected } from '../utils/promises' @@ -16,7 +17,7 @@ import { IndySdkPoolError, IndySdkPoolNotConfiguredError, IndySdkPoolNotFoundErr export interface CachedDidResponse { nymResponse: GetNymResponse - poolId: string + indyNamespace: string } @injectable() @@ -26,40 +27,26 @@ export class IndySdkPoolService { private indySdk: IndySdk private stop$: Subject private fileSystem: FileSystem + private indySdkModuleConfig: IndySdkModuleConfig public constructor( - indySdk: IndySdk, + @inject(IndySdkSymbol) indySdk: IndySdk, @inject(InjectionSymbols.Logger) logger: Logger, @inject(InjectionSymbols.Stop$) stop$: Subject, - @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem + @inject(InjectionSymbols.FileSystem) fileSystem: FileSystem, + indySdkModuleConfig: IndySdkModuleConfig ) { this.logger = logger this.indySdk = indySdk this.fileSystem = fileSystem this.stop$ = stop$ - } + this.indySdkModuleConfig = indySdkModuleConfig - public setPools(poolConfigs: IndySdkPoolConfig[]) { - this.pools = poolConfigs.map( - (poolConfig) => new IndySdkPool(poolConfig, this.indySdk, this.logger, this.stop$, this.fileSystem) + this.pools = this.indySdkModuleConfig.networks.map( + (network) => new IndySdkPool(network, this.indySdk, this.logger, this.stop$, this.fileSystem) ) } - /** - * Create connections to all ledger pools - */ - public async connectToPools() { - const handleArray: number[] = [] - // Sequentially connect to pools so we don't use up too many resources connecting in parallel - for (const pool of this.pools) { - this.logger.debug(`Connecting to pool: ${pool.id}`) - const poolHandle = await pool.connect() - this.logger.debug(`Finished connection to pool: ${pool.id}`) - handleArray.push(poolHandle) - } - return handleArray - } - /** * Get the most appropriate pool for the given did. The algorithm is based on the approach as described in this document: * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit @@ -78,11 +65,11 @@ export class IndySdkPoolService { const cache = agentContext.dependencyManager.resolve(CacheModuleConfig).cache const cachedNymResponse = await cache.get(agentContext, `IndySdkPoolService:${did}`) - const pool = this.pools.find((pool) => pool.id === cachedNymResponse?.poolId) + const pool = this.pools.find((pool) => pool.didIndyNamespace === cachedNymResponse?.indyNamespace) // If we have the nym response with associated pool in the cache, we'll use that if (cachedNymResponse && pool) { - this.logger.trace(`Found ledger id '${pool.id}' for did '${did}' in cache`) + this.logger.trace(`Found ledger '${pool.didIndyNamespace}' for did '${did}' in cache`) return { did: cachedNymResponse.nymResponse, pool } } @@ -126,8 +113,8 @@ export class IndySdkPoolService { await cache.set(agentContext, `IndySdkPoolService:${did}`, { nymResponse: value.did, - poolId: value.pool.id, - }) + indyNamespace: value.pool.didIndyNamespace, + } satisfies CachedDidResponse) return { pool: value.pool, did: value.did } } @@ -290,14 +277,14 @@ export class IndySdkPoolService { private async getDidFromPool(did: string, pool: IndySdkPool): Promise { try { - this.logger.trace(`Get public did '${did}' from ledger '${pool.id}'`) + this.logger.trace(`Get public did '${did}' from ledger '${pool.didIndyNamespace}'`) const request = await this.indySdk.buildGetNymRequest(null, did) - this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.id}'`) + this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.didIndyNamespace}'`) const response = await pool.submitReadRequest(request) const result = await this.indySdk.parseGetNymResponse(response) - this.logger.trace(`Retrieved did '${did}' from ledger '${pool.id}'`, result) + this.logger.trace(`Retrieved did '${did}' from ledger '${pool.didIndyNamespace}'`, result) return { did: result, @@ -305,12 +292,12 @@ export class IndySdkPoolService { response, } } catch (error) { - this.logger.trace(`Error retrieving did '${did}' from ledger '${pool.id}'`, { + this.logger.trace(`Error retrieving did '${did}' from ledger '${pool.didIndyNamespace}'`, { error, did, }) if (isIndyError(error, 'LedgerNotFound')) { - throw new IndySdkPoolNotFoundError(`Did '${did}' not found on ledger ${pool.id}`) + throw new IndySdkPoolNotFoundError(`Did '${did}' not found on ledger ${pool.didIndyNamespace}`) } else { throw isIndyError(error) ? new IndySdkError(error) : error } diff --git a/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts index 2ef487e566..8256200d76 100644 --- a/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts +++ b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts @@ -1,6 +1,5 @@ import type { IndySdkPoolConfig } from '../IndySdkPool' import type { CachedDidResponse } from '../IndySdkPoolService' -import type { AgentContext, Cache } from '@aries-framework/core' import { CacheModuleConfig, @@ -8,46 +7,44 @@ import { SigningProviderRegistry, AriesFrameworkError, } from '@aries-framework/core' +import indySdk from 'indy-sdk' import { Subject } from 'rxjs' -import { getDidResponsesForDid } from '../../../../core/src/modules/ledger/__tests__/didResponses' -import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' import { NodeFileSystem } from '../../../../node/src/NodeFileSystem' +import { IndySdkModuleConfig } from '../../IndySdkModuleConfig' import { IndySdkWallet } from '../../wallet/IndySdkWallet' import { IndySdkPoolService } from '../IndySdkPoolService' import { IndySdkPoolError, IndySdkPoolNotConfiguredError, IndySdkPoolNotFoundError } from '../error' +import { getDidResponsesForDid } from './didResponses' + const pools: IndySdkPoolConfig[] = [ { - id: 'sovrinMain', indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'sovrinBuilder', indyNamespace: 'sovrin:builder', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'sovringStaging', indyNamespace: 'sovrin:staging', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'indicioMain', indyNamespace: 'indicio', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'bcovrinTest', indyNamespace: 'bcovrin:test', isProduction: false, genesisTransactions: 'xxx', @@ -55,17 +52,26 @@ const pools: IndySdkPoolConfig[] = [ }, ] -describe('IndySdkPoolService', () => { - const config = getAgentConfig('IndySdkPoolServiceTest', { - indyLedgers: pools, - }) - let agentContext: AgentContext - let wallet: IndySdkWallet - let poolService: IndySdkPoolService - let cache: Cache +const config = getAgentConfig('IndySdkPoolServiceTest') +const cache = new InMemoryLruCache({ limit: 200 }) +const indySdkModule = new IndySdkModuleConfig({ indySdk, networks: pools }) +const wallet = new IndySdkWallet(indySdk, config.logger, new SigningProviderRegistry([])) + +const agentContext = getAgentContext({ + wallet, + registerInstances: [[CacheModuleConfig, new CacheModuleConfig({ cache })]], +}) + +const poolService = new IndySdkPoolService( + indySdk, + config.logger, + new Subject(), + new NodeFileSystem(), + indySdkModule +) +describe('IndySdkPoolService', () => { beforeAll(async () => { - wallet = new IndySdkWallet(config.agentDependencies.indy, config.logger, new SigningProviderRegistry([])) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) @@ -74,26 +80,14 @@ describe('IndySdkPoolService', () => { await wallet.delete() }) - beforeEach(async () => { - cache = new InMemoryLruCache({ limit: 200 }) - agentContext = getAgentContext({ - registerInstances: [[CacheModuleConfig, new CacheModuleConfig({ cache })]], - }) - poolService = new IndySdkPoolService( - agentDependencies.indy, - config.logger, - new Subject(), - new NodeFileSystem() - ) - - poolService.setPools(pools) - }) - describe('getPoolForDid', () => { it('should throw a IndySdkPoolNotConfiguredError error if no pools are configured on the pool service', async () => { - poolService.setPools([]) + const oldPools = poolService.pools + poolService.pools = [] expect(poolService.getPoolForDid(agentContext, 'some-did')).rejects.toThrow(IndySdkPoolNotConfiguredError) + + poolService.pools = oldPools }) it('should throw a IndySdkPoolError if all ledger requests throw an error other than NotFoundError', async () => { @@ -124,7 +118,7 @@ describe('IndySdkPoolService', () => { const did = 'TL1EaPFCZ8Si5aUrqScBDt' // Only found on one ledger const responses = getDidResponsesForDid(did, pools, { - sovrinMain: '~43X4NhAFqREffK7eWdKgFH', + sovrin: '~43X4NhAFqREffK7eWdKgFH', }) poolService.pools.forEach((pool, index) => { @@ -134,16 +128,16 @@ describe('IndySdkPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinMain') + expect(pool.config.indyNamespace).toBe('sovrin') }) it('should return the first pool with a self certifying DID if at least one did is self certifying ', async () => { const did = 'did:sov:q7ATwTYbQDgiigVijUAej' // Found on one production and one non production ledger const responses = getDidResponsesForDid(did, pools, { - indicioMain: '~43X4NhAFqREffK7eWdKgFH', - bcovrinTest: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', - sovrinBuilder: '~43X4NhAFqREffK7eWdKgFH', + indicio: '~43X4NhAFqREffK7eWdKgFH', + 'bcovrin:test': '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + 'sovrin:builder': '~43X4NhAFqREffK7eWdKgFH', }) poolService.pools.forEach((pool, index) => { @@ -153,15 +147,15 @@ describe('IndySdkPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') }) it('should return the production pool if the did was found on one production and one non production ledger and both DIDs are not self certifying', async () => { const did = 'V6ty6ttM3EjuCtosH6sGtW' // Found on one production and one non production ledger const responses = getDidResponsesForDid(did, pools, { - indicioMain: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', - sovrinBuilder: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + indicio: '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', + 'sovrin:builder': '43X4NhAFqREffK7eWdKgFH43X4NhAFqREffK7eWdKgFH', }) poolService.pools.forEach((pool, index) => { @@ -171,15 +165,15 @@ describe('IndySdkPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('indicioMain') + expect(pool.config.indyNamespace).toBe('indicio') }) it('should return the pool with the self certified did if the did was found on two production ledgers where one did is self certified', async () => { const did = 'VsKV7grR1BUE29mG2Fm2kX' // Found on two production ledgers. Sovrin is self certified const responses = getDidResponsesForDid(did, pools, { - sovrinMain: '~43X4NhAFqREffK7eWdKgFH', - indicioMain: 'kqa2HyagzfMAq42H5f9u3UMwnSBPQx2QfrSyXbUPxMn', + sovrin: '~43X4NhAFqREffK7eWdKgFH', + indicio: 'kqa2HyagzfMAq42H5f9u3UMwnSBPQx2QfrSyXbUPxMn', }) poolService.pools.forEach((pool, index) => { @@ -189,16 +183,16 @@ describe('IndySdkPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinMain') + expect(pool.config.indyNamespace).toBe('sovrin') }) it('should return the first pool with a self certified did if the did was found on three non production ledgers where two DIDs are self certified', async () => { const did = 'HEi9QViXNThGQaDsQ3ptcw' // Found on two non production ledgers. Sovrin is self certified const responses = getDidResponsesForDid(did, pools, { - sovrinBuilder: '~M9kv2Ez61cur7X39DXWh8W', - sovrinStaging: '~M9kv2Ez61cur7X39DXWh8W', - bcovrinTest: '3SeuRm3uYuQDYmHeuMLu1xNHozNTtzS3kbZRFMMCWrX4', + 'sovrin:builder': '~M9kv2Ez61cur7X39DXWh8W', + 'sovrin:staging': '~M9kv2Ez61cur7X39DXWh8W', + 'bcovrin:test': '3SeuRm3uYuQDYmHeuMLu1xNHozNTtzS3kbZRFMMCWrX4', }) poolService.pools.forEach((pool, index) => { @@ -208,7 +202,7 @@ describe('IndySdkPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') }) it('should return the pool from the cache if the did was found in the cache', async () => { @@ -222,20 +216,20 @@ describe('IndySdkPoolService', () => { role: 'ENDORSER', verkey: '~M9kv2Ez61cur7X39DXWh8W', }, - poolId: expectedPool.id, + indyNamespace: expectedPool.indyNamespace, } await cache.set(agentContext, `IndySdkPoolService:${did}`, didResponse) const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe(pool.id) + expect(pool.config.indyNamespace).toBe(pool.didIndyNamespace) }) - it('should set the poolId in the cache if the did was not found in the cache, but resolved later on', async () => { + it('should set the indyNamespace in the cache if the did was not found in the cache, but resolved later on', async () => { const did = 'HEi9QViXNThGQaDsQ3ptcw' // Found on one ledger const responses = getDidResponsesForDid(did, pools, { - sovrinBuilder: '~M9kv2Ez61cur7X39DXWh8W', + 'sovrin:builder': '~M9kv2Ez61cur7X39DXWh8W', }) poolService.pools.forEach((pool, index) => { @@ -245,7 +239,7 @@ describe('IndySdkPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) - expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') expect(pool.config.indyNamespace).toBe('sovrin:builder') expect(await cache.get(agentContext, `IndySdkPoolService:${did}`)).toEqual({ @@ -254,22 +248,25 @@ describe('IndySdkPoolService', () => { verkey: '~M9kv2Ez61cur7X39DXWh8W', role: '0', }, - poolId: 'sovrinBuilder', + indyNamespace: 'sovrin:builder', }) }) }) describe('getPoolForNamespace', () => { it('should throw a IndySdkPoolNotConfiguredError error if no pools are configured on the pool service', async () => { - poolService.setPools([]) + const oldPools = poolService.pools + poolService.pools = [] expect(() => poolService.getPoolForNamespace()).toThrow(IndySdkPoolNotConfiguredError) + + poolService.pools = oldPools }) it('should return the first pool if indyNamespace is not provided', async () => { const expectedPool = pools[0] - expect(poolService.getPoolForNamespace().id).toEqual(expectedPool.id) + expect(poolService.getPoolForNamespace().didIndyNamespace).toEqual(expectedPool.indyNamespace) }) it('should throw a IndySdkPoolNotFoundError error if any of the pools did not have the provided indyNamespace', async () => { @@ -296,7 +293,7 @@ describe('IndySdkPoolService', () => { const pool = poolService.getPoolForNamespace(indyNameSpace) - expect(pool.id).toEqual(expectedPool.id) + expect(pool.didIndyNamespace).toEqual(expectedPool.indyNamespace) }) }) diff --git a/packages/core/src/modules/ledger/__tests__/didResponses.ts b/packages/indy-sdk/src/ledger/__tests__/didResponses.ts similarity index 94% rename from packages/core/src/modules/ledger/__tests__/didResponses.ts rename to packages/indy-sdk/src/ledger/__tests__/didResponses.ts index bde086e073..4d3dac6596 100644 --- a/packages/core/src/modules/ledger/__tests__/didResponses.ts +++ b/packages/indy-sdk/src/ledger/__tests__/didResponses.ts @@ -1,4 +1,4 @@ -import type { IndyPoolConfig } from '../IndyPool' +import type { IndySdkPoolConfig } from '../IndySdkPool' import type * as Indy from 'indy-sdk' // eslint-disable-next-line @typescript-eslint/ban-ts-comment @@ -42,11 +42,11 @@ export function getDidResponse({ did, verkey }: { did: string; verkey: string }) export function getDidResponsesForDid( did: string, - pools: IndyPoolConfig[], + pools: IndySdkPoolConfig[], responses: { [key: string]: string | undefined } ) { return pools.map((pool) => { - const verkey = responses[pool.id] + const verkey = responses[pool.indyNamespace] if (verkey) { return () => Promise.resolve(getDidResponse({ did, verkey })) diff --git a/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts b/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts index 7a8855c9d5..a01638e652 100644 --- a/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts +++ b/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts @@ -1,30 +1,28 @@ import type { IndySdk } from '../../types' -import type { AgentContext, TagsBase } from '@aries-framework/core' +import type { TagsBase } from '@aries-framework/core' -import { SigningProviderRegistry, RecordDuplicateError, RecordNotFoundError } from '@aries-framework/core' +import { RecordDuplicateError, RecordNotFoundError, SigningProviderRegistry } from '@aries-framework/core' +import * as indySdk from 'indy-sdk' import { TestRecord } from '../../../../core/src/storage/__tests__/TestRecord' -import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' import { IndySdkWallet } from '../../wallet/IndySdkWallet' import { IndySdkStorageService } from '../IndySdkStorageService' -describe('IndySdkStorageService', () => { - let wallet: IndySdkWallet - let indy: IndySdk - let storageService: IndySdkStorageService - let agentContext: AgentContext +const agentConfig = getAgentConfig('IndySdkStorageServiceTest') +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) + +const agentContext = getAgentContext({ + wallet, + agentConfig, +}) +const storageService = new IndySdkStorageService(indySdk) + +describe('IndySdkStorageService', () => { beforeEach(async () => { - indy = agentDependencies.indy - const agentConfig = getAgentConfig('IndySdkStorageServiceTest') - wallet = new IndySdkWallet(indy, agentConfig.logger, new SigningProviderRegistry([])) - agentContext = getAgentContext({ - wallet, - agentConfig, - }) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) - storageService = new IndySdkStorageService(indy) }) afterEach(async () => { @@ -57,7 +55,7 @@ describe('IndySdkStorageService', () => { }, }) - const retrieveRecord = await indy.getWalletRecord(wallet.handle, record.type, record.id, { + const retrieveRecord = await indySdk.getWalletRecord(wallet.handle, record.type, record.id, { retrieveType: true, retrieveTags: true, }) @@ -74,7 +72,7 @@ describe('IndySdkStorageService', () => { }) it('should correctly transform tag values from string after retrieving', async () => { - await indy.addWalletRecord(wallet.handle, TestRecord.type, 'some-id', '{}', { + await indySdk.addWalletRecord(wallet.handle, TestRecord.type, 'some-id', '{}', { someBoolean: '1', someOtherBoolean: '0', someStringValue: 'string', @@ -237,13 +235,13 @@ describe('IndySdkStorageService', () => { it('correctly transforms an advanced query into a valid WQL query', async () => { const indySpy = jest.fn() - const storageServiceWithoutIndy = new IndySdkStorageService({ + const storageServiceWithoutIndySdk = new IndySdkStorageService({ openWalletSearch: indySpy, fetchWalletSearchNextRecords: jest.fn(() => ({ records: undefined })), closeWalletSearch: jest.fn(), } as unknown as IndySdk) - await storageServiceWithoutIndy.findByQuery(agentContext, TestRecord, { + await storageServiceWithoutIndySdk.findByQuery(agentContext, TestRecord, { $and: [ { $or: [{ myTag: true }, { myTag: false }], diff --git a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts new file mode 100644 index 0000000000..1bb266a399 --- /dev/null +++ b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts @@ -0,0 +1,131 @@ +import type { IndySdkSovDidCreateOptions } from '../src/dids/IndySdkSovDidRegistrar' + +import { Agent, TypedArrayEncoder, convertPublicKeyToX25519, JsonTransformer } from '@aries-framework/core' +import { getAgentOptions, genesisPath } from '@aries-framework/core/tests/helpers' +import { generateKeyPairFromSeed } from '@stablelib/ed25519' + +import { indyDidFromPublicKeyBase58 } from '../src/utils/did' + +const agentOptions = getAgentOptions('Faber Dids Registrar', { + indyLedgers: [ + { + id: `localhost`, + isProduction: false, + genesisPath, + indyNamespace: 'localhost', + transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, + }, + ], +}) + +describe('dids', () => { + let agent: Agent + + beforeAll(async () => { + agent = new Agent(agentOptions) + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should create a did:sov did', async () => { + // Generate a seed and the indy did. This allows us to create a new did every time + // but still check if the created output document is as expected. + const seed = Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + + const publicKeyEd25519 = generateKeyPairFromSeed(TypedArrayEncoder.fromString(seed)).publicKey + const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(publicKeyEd25519)) + const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyEd25519) + const indyDid = indyDidFromPublicKeyBase58(ed25519PublicKeyBase58) + + const did = await agent.dids.create({ + method: 'sov', + options: { + submitterDid: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', + alias: 'Alias', + endpoints: { + endpoint: 'https://example.com/endpoint', + types: ['DIDComm', 'did-communication', 'endpoint'], + routingKeys: ['a-routing-key'], + }, + }, + secret: { + seed, + }, + }) + + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocumentMetadata: { + qualifiedIndyDid: `did:indy:localhost:${indyDid}`, + }, + didRegistrationMetadata: { + didIndyNamespace: 'localhost', + }, + didState: { + state: 'finished', + did: `did:sov:${indyDid}`, + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + id: `did:sov:${indyDid}#key-1`, + type: 'Ed25519VerificationKey2018', + controller: `did:sov:${indyDid}`, + publicKeyBase58: ed25519PublicKeyBase58, + }, + { + id: `did:sov:${indyDid}#key-agreement-1`, + type: 'X25519KeyAgreementKey2019', + controller: `did:sov:${indyDid}`, + publicKeyBase58: x25519PublicKeyBase58, + }, + ], + service: [ + { + id: `did:sov:${indyDid}#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + accept: ['didcomm/aip2;env=rfc19'], + id: `did:sov:${indyDid}#did-communication`, + priority: 0, + recipientKeys: [`did:sov:${indyDid}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + { + accept: ['didcomm/v2'], + id: `did:sov:${indyDid}#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + }, + ], + authentication: [`did:sov:${indyDid}#key-1`], + assertionMethod: [`did:sov:${indyDid}#key-1`], + keyAgreement: [`did:sov:${indyDid}#key-agreement-1`], + capabilityInvocation: undefined, + capabilityDelegation: undefined, + id: `did:sov:${indyDid}`, + }, + secret: { + seed, + }, + }, + }) + }) +}) diff --git a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..3d85fa132e --- /dev/null +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -0,0 +1,93 @@ +import type { IndySdkSovDidCreateOptions } from '../src/dids/IndySdkSovDidRegistrar' + +import { Agent, AriesFrameworkError, JsonTransformer } from '@aries-framework/core' +import { uuid } from '@aries-framework/core/src/utils/uuid' +import { genesisPath, getAgentOptions, taaAcceptanceMechanism, taaVersion } from '@aries-framework/core/tests/helpers' +import indySdk from 'indy-sdk' + +import { IndySdkModule } from '../src' + +const agent = new Agent( + getAgentOptions( + 'Indy SDK Sov DID resolver', + {}, + { + indySdk: new IndySdkModule({ + indySdk, + networks: [ + { + isProduction: false, + genesisPath, + indyNamespace: `pool:localtest:${uuid()}`, + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, + }, + ], + }), + } + ) +) + +describe('Indy SDK Sov DID resolver', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should resolve a did:sov did', async () => { + const createResult = await agent.dids.create({ + method: 'sov', + options: { + submitterDid: 'did:sov:TL1EaPFCZ8Si5aUrqScBDt', + alias: 'Alias', + role: 'TRUSTEE', + }, + }) + + // Terrible, but the did can't be immediately resolved, so we need to wait a bit + await new Promise((res) => setTimeout(res, 1000)) + + if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') + const didResult = await agent.dids.resolve(createResult.didState.did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: createResult.didState.did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: createResult.didState.did, + id: `${createResult.didState.did}#key-1`, + publicKeyBase58: expect.any(String), + }, + { + controller: createResult.didState.did, + type: 'X25519KeyAgreementKey2019', + id: `${createResult.didState.did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${createResult.didState.did}#key-1`], + assertionMethod: [`${createResult.didState.did}#key-1`], + keyAgreement: [`${createResult.didState.did}#key-agreement-1`], + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 5e58035b32..a693aa2f9c 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -1,7 +1,6 @@ import type { AgentDependencies } from '@aries-framework/core' import { EventEmitter } from 'events' -import * as indy from 'indy-sdk' import fetch from 'node-fetch' import WebSocket from 'ws' @@ -15,7 +14,6 @@ const agentDependencies: AgentDependencies = { fetch, EventEmitterClass: EventEmitter, WebSocketClass: WebSocket, - indy, } export { diff --git a/packages/react-native/src/index.ts b/packages/react-native/src/index.ts index 4b7e0a3eb9..ea76cafbe0 100644 --- a/packages/react-native/src/index.ts +++ b/packages/react-native/src/index.ts @@ -4,10 +4,6 @@ import '@azure/core-asynciterator-polyfill' import type { AgentDependencies } from '@aries-framework/core' import { EventEmitter } from 'events' -// Eslint complains indy-sdk-react-native has no default export -// But that's not true -// eslint-disable-next-line import/default -import indy from 'indy-sdk-react-native' import { ReactNativeFileSystem } from './ReactNativeFileSystem' @@ -19,7 +15,6 @@ const agentDependencies: AgentDependencies = { fetch, EventEmitterClass: EventEmitter, WebSocketClass: WebSocket, - indy, } export { agentDependencies } diff --git a/tests/InMemoryStorageService.ts b/tests/InMemoryStorageService.ts index cd4415a2e5..43b2ce57a2 100644 --- a/tests/InMemoryStorageService.ts +++ b/tests/InMemoryStorageService.ts @@ -18,7 +18,9 @@ interface StorageRecord { } @injectable() -export class InMemoryStorageService implements StorageService { +export class InMemoryStorageService = BaseRecord> + implements StorageService +{ public records: { [id: string]: StorageRecord } public constructor(records: { [id: string]: StorageRecord } = {}) { diff --git a/tests/e2e-askar-indy-sdk-wallet-subject.test.ts b/tests/e2e-askar-indy-sdk-wallet-subject.test.ts index b7d4233738..7c4b3e57f3 100644 --- a/tests/e2e-askar-indy-sdk-wallet-subject.test.ts +++ b/tests/e2e-askar-indy-sdk-wallet-subject.test.ts @@ -9,8 +9,7 @@ import { Agent, DependencyManager, InjectionSymbols } from '@aries-framework/cor import { IndySdkModule, IndySdkStorageService, IndySdkWallet } from '@aries-framework/indy-sdk' import { SubjectInboundTransport } from './transport/SubjectInboundTransport' - -import { agentDependencies } from '@aries-framework/node' +import indySdk from 'indy-sdk' import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' @@ -37,34 +36,40 @@ describe.skip('E2E Askar-Indy SDK Wallet Subject tests', () => { senderDependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) senderDependencyManager.registerSingleton(InjectionSymbols.StorageService, IndySdkStorageService) senderAgent = new Agent( - { - ...getAgentOptions('E2E Wallet Subject Sender Indy', { endpoints: ['rxjs:sender'] }), - modules: { indySdk: new IndySdkModule({ indySdk: agentDependencies.indy }) }, - }, + getAgentOptions( + 'E2E Wallet Subject Sender Indy', + { endpoints: ['rxjs:sender'] }, + { indySdk: new IndySdkModule({ indySdk }) } + ), senderDependencyManager ) // Recipient is an Agent using Askar Wallet - recipientAgent = new Agent({ - ...getAgentOptions('E2E Wallet Subject Recipient Askar', { endpoints: ['rxjs:recipient'] }), - modules: { askar: new AskarModule() }, - }) + recipientAgent = new Agent( + getAgentOptions( + 'E2E Wallet Subject Recipient Askar', + { endpoints: ['rxjs:recipient'] }, + { askar: new AskarModule() } + ) + ) await e2eWalletTest(senderAgent, recipientAgent) }) test('Wallet Subject flow - Askar Sender / Askar Recipient ', async () => { // Sender is an Agent using Askar Wallet - senderAgent = new Agent({ - ...getAgentOptions('E2E Wallet Subject Sender Askar', { endpoints: ['rxjs:sender'] }), - modules: { askar: new AskarModule() }, - }) + senderAgent = new Agent( + getAgentOptions('E2E Wallet Subject Sender Askar', { endpoints: ['rxjs:sender'] }, { askar: new AskarModule() }) + ) // Recipient is an Agent using Askar Wallet - recipientAgent = new Agent({ - ...getAgentOptions('E2E Wallet Subject Recipient Askar', { endpoints: ['rxjs:recipient'] }), - modules: { askar: new AskarModule() }, - }) + recipientAgent = new Agent( + getAgentOptions( + 'E2E Wallet Subject Recipient Askar', + { endpoints: ['rxjs:recipient'] }, + { askar: new AskarModule() } + ) + ) await e2eWalletTest(senderAgent, recipientAgent) }) @@ -75,10 +80,11 @@ describe.skip('E2E Askar-Indy SDK Wallet Subject tests', () => { senderDependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) senderDependencyManager.registerSingleton(InjectionSymbols.StorageService, IndySdkStorageService) senderAgent = new Agent( - { - ...getAgentOptions('E2E Wallet Subject Sender Indy', { endpoints: ['rxjs:sender'] }), - modules: { indySdk: new IndySdkModule({ indySdk: agentDependencies.indy }) }, - }, + getAgentOptions( + 'E2E Wallet Subject Sender Indy', + { endpoints: ['rxjs:sender'] }, + { indySdk: new IndySdkModule({ indySdk }) } + ), senderDependencyManager ) @@ -87,10 +93,11 @@ describe.skip('E2E Askar-Indy SDK Wallet Subject tests', () => { recipientDependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) recipientDependencyManager.registerSingleton(InjectionSymbols.StorageService, IndySdkStorageService) recipientAgent = new Agent( - { - ...getAgentOptions('E2E Wallet Subject Recipient Indy', { endpoints: ['rxjs:recipient'] }), - modules: { indySdk: new IndySdkModule({ indySdk: agentDependencies.indy }) }, - }, + getAgentOptions( + 'E2E Wallet Subject Recipient Indy', + { endpoints: ['rxjs:recipient'] }, + { indySdk: new IndySdkModule({ indySdk }) } + ), recipientDependencyManager ) diff --git a/tests/e2e-http.test.ts b/tests/e2e-http.test.ts index 4bd2396d41..a3b529e8e3 100644 --- a/tests/e2e-http.test.ts +++ b/tests/e2e-http.test.ts @@ -4,30 +4,45 @@ import { e2eTest } from './e2e-test' import { HttpOutboundTransport, Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { HttpInboundTransport } from '@aries-framework/node' +import { AnonCredsTestsAgent, getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' -const recipientAgentOptions = getAgentOptions('E2E HTTP Recipient', { +const modules = getLegacyAnonCredsModules({ autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) +const recipientAgentOptions = getAgentOptions( + 'E2E HTTP Recipient', + { + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + modules +) + const mediatorPort = 3000 -const mediatorAgentOptions = getAgentOptions('E2E HTTP Mediator', { - endpoints: [`http://localhost:${mediatorPort}`], - autoAcceptMediationRequests: true, -}) +const mediatorAgentOptions = getAgentOptions( + 'E2E HTTP Mediator', + { + endpoints: [`http://localhost:${mediatorPort}`], + autoAcceptMediationRequests: true, + }, + modules +) const senderPort = 3001 -const senderAgentOptions = getAgentOptions('E2E HTTP Sender', { - endpoints: [`http://localhost:${senderPort}`], - mediatorPollingInterval: 1000, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const senderAgentOptions = getAgentOptions( + 'E2E HTTP Sender', + { + endpoints: [`http://localhost:${senderPort}`], + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + modules +) describe('E2E HTTP tests', () => { - let recipientAgent: Agent - let mediatorAgent: Agent - let senderAgent: Agent + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent beforeEach(async () => { recipientAgent = new Agent(recipientAgentOptions) diff --git a/tests/e2e-subject.test.ts b/tests/e2e-subject.test.ts index 6f550435fc..f377bab66b 100644 --- a/tests/e2e-subject.test.ts +++ b/tests/e2e-subject.test.ts @@ -10,26 +10,41 @@ import { SubjectInboundTransport } from './transport/SubjectInboundTransport' import { Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' +import { AnonCredsTestsAgent, getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' -const recipientAgentOptions = getAgentOptions('E2E Subject Recipient', { +const modules = getLegacyAnonCredsModules({ autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) -const mediatorAgentOptions = getAgentOptions('E2E Subject Mediator', { - endpoints: ['rxjs:mediator'], - autoAcceptMediationRequests: true, -}) -const senderAgentOptions = getAgentOptions('E2E Subject Sender', { - endpoints: ['rxjs:sender'], - mediatorPollingInterval: 1000, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) +const recipientAgentOptions = getAgentOptions( + 'E2E Subject Recipient', + { + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + modules +) +const mediatorAgentOptions = getAgentOptions( + 'E2E Subject Mediator', + { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }, + modules +) +const senderAgentOptions = getAgentOptions( + 'E2E Subject Sender', + { + endpoints: ['rxjs:sender'], + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + modules +) + describe('E2E Subject tests', () => { - let recipientAgent: Agent - let mediatorAgent: Agent - let senderAgent: Agent + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent beforeEach(async () => { recipientAgent = new Agent(recipientAgentOptions) diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index 0f4c07e6da..5986806e39 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -1,19 +1,29 @@ -import type { Agent } from '@aries-framework/core' +import type { Agent, BaseEvent, CredentialStateChangedEvent, ProofStateChangedEvent } from '@aries-framework/core' import { sleep } from '../packages/core/src/utils/sleep' -import { issueCredential, makeConnection, prepareForIssuance, presentProof } from '../packages/core/tests/helpers' +import { + issueLegacyAnonCredsCredential, + presentLegacyAnonCredsProof, + prepareForAnonCredsIssuance, + AnonCredsTestsAgent, +} from '../packages/anoncreds/tests/legacyAnonCredsSetup' +import { makeConnection } from '../packages/core/tests/helpers' import { V1CredentialPreview, CredentialState, MediationState, ProofState } from '@aries-framework/core' +import { ReplaySubject } from 'rxjs' export async function e2eTest({ mediatorAgent, recipientAgent, senderAgent, }: { - mediatorAgent: Agent - recipientAgent: Agent - senderAgent: Agent + mediatorAgent: AnonCredsTestsAgent + recipientAgent: AnonCredsTestsAgent + senderAgent: AnonCredsTestsAgent }) { + const senderReplay = new ReplaySubject() + const recipientReplay = new ReplaySubject() + // Make connection between mediator and recipient const [mediatorRecipientConnection, recipientMediatorConnection] = await makeConnection(mediatorAgent, recipientAgent) expect(recipientMediatorConnection).toBeConnectedWith(mediatorRecipientConnection) @@ -33,13 +43,20 @@ export async function e2eTest({ expect(recipientSenderConnection).toBeConnectedWith(senderRecipientConnection) // Issue credential from sender to recipient - const { definition } = await prepareForIssuance(senderAgent, ['name', 'age', 'dateOfBirth']) - const { holderCredential, issuerCredential } = await issueCredential({ + const { credentialDefinition } = await prepareForAnonCredsIssuance(senderAgent, { + attributeNames: ['name', 'age', 'dateOfBirth'], + // TODO: update to dynamic created did + issuerId: senderAgent.publicDid?.did as string, + }) + const { holderCredentialExchangeRecord, issuerCredentialExchangeRecord } = await issueLegacyAnonCredsCredential({ issuerAgent: senderAgent, + issuerReplay: senderReplay, holderAgent: recipientAgent, - issuerConnectionId: senderRecipientConnection.id, - credentialTemplate: { - credentialDefinitionId: definition.id, + holderReplay: recipientReplay, + + issuerHolderConnectionId: senderRecipientConnection.id, + offer: { + credentialDefinitionId: credentialDefinition.credentialDefinitionId, attributes: V1CredentialPreview.fromRecord({ name: 'John', age: '25', @@ -49,39 +66,46 @@ export async function e2eTest({ }, }) - expect(holderCredential.state).toBe(CredentialState.Done) - expect(issuerCredential.state).toBe(CredentialState.Done) + expect(holderCredentialExchangeRecord.state).toBe(CredentialState.Done) + expect(issuerCredentialExchangeRecord.state).toBe(CredentialState.Done) // Present Proof from recipient to sender - const definitionRestriction = [ - { - credentialDefinitionId: definition.id, - }, - ] - const { holderProof, verifierProof } = await presentProof({ + const { holderProofExchangeRecord, verifierProofExchangeRecord } = await presentLegacyAnonCredsProof({ verifierAgent: senderAgent, + verifierReplay: senderReplay, + holderAgent: recipientAgent, - verifierConnectionId: senderRecipientConnection.id, - presentationTemplate: { + holderReplay: recipientReplay, + + verifierHolderConnectionId: senderRecipientConnection.id, + request: { attributes: { name: { name: 'name', - restrictions: definitionRestriction, + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], }, }, predicates: { olderThan21: { name: 'age', - restrictions: definitionRestriction, - predicateType: '<=', - predicateValue: 20000712, + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + p_type: '<=', + p_value: 20000712, }, }, }, }) - expect(holderProof.state).toBe(ProofState.Done) - expect(verifierProof.state).toBe(ProofState.Done) + expect(holderProofExchangeRecord.state).toBe(ProofState.Done) + expect(verifierProofExchangeRecord.state).toBe(ProofState.Done) // We want to stop the mediator polling before the agent is shutdown. await recipientAgent.mediationRecipient.stopMessagePickup() diff --git a/tests/e2e-ws-pickup-v2.test.ts b/tests/e2e-ws-pickup-v2.test.ts index 45c641c7d7..bca7afff86 100644 --- a/tests/e2e-ws-pickup-v2.test.ts +++ b/tests/e2e-ws-pickup-v2.test.ts @@ -4,31 +4,47 @@ import { e2eTest } from './e2e-test' import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' +import { AnonCredsTestsAgent, getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' -const recipientOptions = getAgentOptions('E2E WS Pickup V2 Recipient ', { +const modules = getLegacyAnonCredsModules({ autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, }) +const recipientOptions = getAgentOptions( + 'E2E WS Pickup V2 Recipient ', + { + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, + }, + modules +) + // FIXME: port numbers should not depend on availability from other test suites that use web sockets const mediatorPort = 4100 -const mediatorOptions = getAgentOptions('E2E WS Pickup V2 Mediator', { - endpoints: [`ws://localhost:${mediatorPort}`], - autoAcceptMediationRequests: true, -}) +const mediatorOptions = getAgentOptions( + 'E2E WS Pickup V2 Mediator', + { + endpoints: [`ws://localhost:${mediatorPort}`], + autoAcceptMediationRequests: true, + }, + modules +) const senderPort = 4101 -const senderOptions = getAgentOptions('E2E WS Pickup V2 Sender', { - endpoints: [`ws://localhost:${senderPort}`], - mediatorPollingInterval: 1000, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, -}) +const senderOptions = getAgentOptions( + 'E2E WS Pickup V2 Sender', + { + endpoints: [`ws://localhost:${senderPort}`], + mediatorPollingInterval: 1000, + + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, + }, + modules +) describe('E2E WS Pickup V2 tests', () => { - let recipientAgent: Agent - let mediatorAgent: Agent - let senderAgent: Agent + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent beforeEach(async () => { recipientAgent = new Agent(recipientOptions) diff --git a/tests/e2e-ws.test.ts b/tests/e2e-ws.test.ts index e0bd5f27ab..6f375270f0 100644 --- a/tests/e2e-ws.test.ts +++ b/tests/e2e-ws.test.ts @@ -4,30 +4,45 @@ import { e2eTest } from './e2e-test' import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' +import { AnonCredsTestsAgent, getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' -const recipientAgentOptions = getAgentOptions('E2E WS Recipient ', { +const modules = getLegacyAnonCredsModules({ autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) +const recipientAgentOptions = getAgentOptions( + 'E2E WS Recipient ', + { + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + modules +) + const mediatorPort = 4000 -const mediatorAgentOptions = getAgentOptions('E2E WS Mediator', { - endpoints: [`ws://localhost:${mediatorPort}`], - autoAcceptMediationRequests: true, -}) +const mediatorAgentOptions = getAgentOptions( + 'E2E WS Mediator', + { + endpoints: [`ws://localhost:${mediatorPort}`], + autoAcceptMediationRequests: true, + }, + modules +) const senderPort = 4001 -const senderAgentOptions = getAgentOptions('E2E WS Sender', { - endpoints: [`ws://localhost:${senderPort}`], - mediatorPollingInterval: 1000, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const senderAgentOptions = getAgentOptions( + 'E2E WS Sender', + { + endpoints: [`ws://localhost:${senderPort}`], + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + modules +) describe('E2E WS tests', () => { - let recipientAgent: Agent - let mediatorAgent: Agent - let senderAgent: Agent + let recipientAgent: AnonCredsTestsAgent + let mediatorAgent: AnonCredsTestsAgent + let senderAgent: AnonCredsTestsAgent beforeEach(async () => { recipientAgent = new Agent(recipientAgentOptions) From 5855c347025fed6ab66d7cbcdd217a76eece7c3e Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 13 Feb 2023 16:55:11 +0100 Subject: [PATCH 06/22] temp Signed-off-by: Timo Glastra --- package.json | 2 +- .../credentials/v1/V1CredentialProtocol.ts | 28 +-- .../V1CredentialProtocolCred.test.ts | 128 ++++++------- .../V1CredentialProtocolProposeOffer.test.ts | 20 +- .../v1-connectionless-credentials.e2e.test.ts | 42 +++-- .../v1-credentials-auto-accept.e2e.test.ts | 178 +++++++++--------- .../v1/__tests__/v1-credentials.e2e.test.ts | 58 +++--- .../credentialPreviewAttributes.test.ts | 143 ++++++++++++++ .../anoncreds/src/utils/composeAutoAccept.ts | 21 +++ .../src/utils/credentialPreviewAttributes.ts | 27 +++ packages/anoncreds/src/utils/index.ts | 2 + .../anoncreds/tests/legacyAnonCredsSetup.ts | 3 +- packages/core/src/index.ts | 4 +- .../modules/credentials/CredentialsModule.ts | 3 +- .../src/modules/credentials/protocol/index.ts | 5 + .../__tests__/IndySdkSovDidRegistrar.test.ts | 9 +- .../__tests__/IndySdkSovDidResolver.test.ts | 10 +- yarn.lock | 2 +- 18 files changed, 443 insertions(+), 242 deletions(-) create mode 100644 packages/anoncreds/src/utils/__tests__/credentialPreviewAttributes.test.ts create mode 100644 packages/anoncreds/src/utils/composeAutoAccept.ts create mode 100644 packages/anoncreds/src/utils/credentialPreviewAttributes.ts diff --git a/package.json b/package.json index 582f91a77e..c56b68e398 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "jest": "^27.0.4", "lerna": "^4.0.0", "prettier": "^2.3.1", - "rxjs": "^7.2.0", + "rxjs": "^7.8.0", "ts-jest": "^27.0.3", "ts-node": "^10.0.0", "tsconfig-paths": "^4.1.2", diff --git a/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts index ba6b913ce5..1b10e6eba4 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts @@ -25,15 +25,15 @@ import { CredentialProblemReportReason, CredentialsModuleConfig, AutoAcceptCredential, + utils, + DidCommMessageRepository, + DidCommMessageRole, + BaseCredentialProtocol, + isLinkedAttachment, } from '@aries-framework/core' -import { BaseCredentialProtocol } from '@aries-framework/core/src/modules/credentials/protocol/BaseCredentialProtocol' -import { composeAutoAccept } from '@aries-framework/core/src/modules/credentials/util/composeAutoAccept' -import { arePreviewAttributesEqual } from '@aries-framework/core/src/modules/credentials/util/previewAttributes' -import { DidCommMessageRepository, DidCommMessageRole } from '@aries-framework/core/src/storage' -import { isLinkedAttachment } from '@aries-framework/core/src/utils/attachment' -import { uuid } from '@aries-framework/core/src/utils/uuid' import { AnonCredsCredentialProposal } from '../../../models/AnonCredsCredentialProposal' +import { composeCredentialAutoAccept, areCredentialPreviewAttributesEqual } from '../../../utils' import { V1ProposeCredentialHandler, @@ -135,7 +135,7 @@ export class V1CredentialProtocol // Create record const credentialRecord = new CredentialExchangeRecord({ connectionId: connectionRecord.id, - threadId: uuid(), + threadId: utils.uuid(), state: CredentialState.ProposalSent, linkedAttachments: linkedAttachments?.map((linkedAttachment) => linkedAttachment.attachment), autoAcceptCredential, @@ -430,7 +430,7 @@ export class V1CredentialProtocol // Create record const credentialRecord = new CredentialExchangeRecord({ connectionId: connectionRecord?.id, - threadId: uuid(), + threadId: utils.uuid(), linkedAttachments: credentialFormats.indy.linkedAttachments?.map( (linkedAttachments) => linkedAttachments.attachment ), @@ -1037,7 +1037,7 @@ export class V1CredentialProtocol const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1059,7 +1059,7 @@ export class V1CredentialProtocol if (credentialOfferJson.cred_def_id !== proposalMessage.credentialDefinitionId) return false // Check if preview values match - return arePreviewAttributesEqual( + return areCredentialPreviewAttributesEqual( proposalMessage.credentialPreview.attributes, offerMessage.credentialPreview.attributes ) @@ -1076,7 +1076,7 @@ export class V1CredentialProtocol const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1098,7 +1098,7 @@ export class V1CredentialProtocol if (credentialOfferJson.cred_def_id !== proposalMessage.credentialDefinitionId) return false // Check if preview values match - return arePreviewAttributesEqual( + return areCredentialPreviewAttributesEqual( proposalMessage.credentialPreview.attributes, offerMessage.credentialPreview.attributes ) @@ -1115,7 +1115,7 @@ export class V1CredentialProtocol const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1150,7 +1150,7 @@ export class V1CredentialProtocol const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts index 0c56bb46f3..809a8c7f18 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts @@ -1,37 +1,39 @@ -import type { AgentContext } from '../../../../../agent' -import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { GetAgentMessageOptions } from '../../../../../storage/didcomm/DidCommMessageRepository' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { IndyCredentialViewMetadata } from '../../../formats/indy/models' -import type { CredentialPreviewAttribute } from '../../../../models' -import type { CustomCredentialTags } from '../../../repository/CredentialExchangeRecord' +import type { + AgentContext, + CustomCredentialTags, + CredentialPreviewAttribute, + AgentConfig, + CredentialStateChangedEvent, +} from '@aries-framework/core' +import { + EventEmitter, + CredentialRepository, + DidCommMessageRepository, + ConnectionService, + DidExchangeState, + Attachment, + AttachmentData, + JsonEncoder, + DidCommMessageRecord, + DidCommMessageRole, + AriesFrameworkError, + CredentialState, + CredentialExchangeRecord, + CredentialMetadataKeys, + CredentialFormatSpec, + AutoAcceptCredential, + JsonTransformer, + InboundMessageContext, + CredentialEventTypes, + AckStatus, + CredentialProblemReportReason, +} from '@aries-framework/core' import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' -import { EventEmitter } from '../../../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../../error' -import { DidCommMessageRecord, DidCommMessageRole } from '../../../../../storage' -import { DidCommMessageRepository } from '../../../../../storage/didcomm/DidCommMessageRepository' -import { JsonTransformer } from '../../../../../utils' -import { JsonEncoder } from '../../../../../utils/JsonEncoder' -import { uuid } from '../../../../../utils/uuid' -import { AckStatus } from '../../../../common' -import { DidExchangeState } from '../../../../connections' -import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { CredentialEventTypes } from '../../../CredentialEvents' -import { credDef, credReq } from '../../../__tests__/fixtures' -import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' -import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' -import { IndyCredentialUtils } from '../../../formats/indy/IndyCredentialUtils' -import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' -import { CredentialFormatSpec } from '../../../models/CredentialFormatSpec' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { CredentialMetadataKeys } from '../../../repository/CredentialMetadataTypes' -import { CredentialRepository } from '../../../repository/CredentialRepository' +import { getMockConnection, getAgentConfig, getAgentContext, mockFunction } from '../../../../../../core/tests/helpers' +import { LegacyIndyCredentialFormatService } from '../../../../formats/LegacyIndyCredentialFormatService' +import { convertAttributesToCredentialValues } from '../../../../utils/credential' import { V1CredentialProtocol } from '../V1CredentialProtocol' import { INDY_CREDENTIAL_ATTACHMENT_ID, @@ -54,18 +56,19 @@ jest.mock('../../../../connections/services/ConnectionService') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock -const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock +const LegacyIndyCredentialFormatServiceMock = + LegacyIndyCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() +const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatServiceMock() const connectionService = new ConnectionServiceMock() // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -indyCredentialFormatService.credentialRecordType = 'indy' +legacyIndyCredentialFormatService.credentialRecordType = 'anoncreds' const connection = getMockConnection({ id: '123', @@ -90,7 +93,7 @@ const requestAttachment = new Attachment({ id: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, mimeType: 'application/json', data: new AttachmentData({ - base64: JsonEncoder.toBase64(credReq), + base64: JsonEncoder.toBase64({}), }), }) @@ -99,14 +102,14 @@ const credentialAttachment = new Attachment({ mimeType: 'application/json', data: new AttachmentData({ base64: JsonEncoder.toBase64({ - values: IndyCredentialUtils.convertAttributesToValues(credentialPreview.attributes), + values: convertAttributesToCredentialValues(credentialPreview.attributes), }), }), }) const credentialProposalMessage = new V1ProposeCredentialMessage({ comment: 'comment', - credentialDefinitionId: credDef.id, + credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', }) const credentialRequestMessage = new V1RequestCredentialMessage({ comment: 'abcd', @@ -129,7 +132,7 @@ const didCommMessageRecord = new DidCommMessageRecord({ }) // eslint-disable-next-line @typescript-eslint/no-explicit-any -const getAgentMessageMock = async (agentContext: AgentContext, options: GetAgentMessageOptions) => { +const getAgentMessageMock = async (agentContext: AgentContext, options: { messageClass: any }) => { if (options.messageClass === V1ProposeCredentialMessage) { return credentialProposalMessage } @@ -160,7 +163,7 @@ const mockCredentialRecord = ({ indyCredentialRevocationId, }: { state?: CredentialState - metadata?: IndyCredentialViewMetadata & { indyRequest: Record } + metadata?: { indyRequest: Record } tags?: CustomCredentialTags threadId?: string connectionId?: string @@ -174,7 +177,7 @@ const mockCredentialRecord = ({ id, credentialAttributes: credentialAttributes || credentialPreview.attributes, state: state || CredentialState.OfferSent, - threadId: threadId ?? uuid(), + threadId: threadId ?? '809dd7ec-f0e7-4b97-9231-7a3615af6139', connectionId: connectionId ?? '123', credentials: [ { @@ -190,18 +193,6 @@ const mockCredentialRecord = ({ credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) } - if (metadata?.schemaId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - schemaId: metadata.schemaId, - }) - } - - if (metadata?.credentialDefinitionId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: metadata.credentialDefinitionId, - }) - } - if (indyCredentialRevocationId || indyRevocationRegistryId) { credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { indyCredentialRevocationId, @@ -243,7 +234,7 @@ describe('V1CredentialProtocol', () => { didCommMessageRecord, ]) - credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: indyCredentialFormatService }) + credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: legacyIndyCredentialFormatService }) }) afterEach(() => { @@ -259,14 +250,8 @@ describe('V1CredentialProtocol', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - const credentialFormats = { - indy: { - holderDid: 'did:sov:123456789abcdefghi', - }, - } - // mock resolved format call - mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptOffer).mockResolvedValue({ attachment: requestAttachment, format: new CredentialFormatSpec({ format: 'indy', @@ -279,7 +264,6 @@ describe('V1CredentialProtocol', () => { comment: 'hello', autoAcceptCredential: AutoAcceptCredential.Never, credentialRecord, - credentialFormats, }) // then @@ -298,7 +282,7 @@ describe('V1CredentialProtocol', () => { 'requests~attach': [JsonTransformer.toJSON(requestAttachment)], }) expect(credentialRepository.update).toHaveBeenCalledTimes(1) - expect(indyCredentialFormatService.acceptOffer).toHaveBeenCalledWith(agentContext, { + expect(legacyIndyCredentialFormatService.acceptOffer).toHaveBeenCalledWith(agentContext, { credentialRecord, attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, offerAttachment, @@ -323,7 +307,7 @@ describe('V1CredentialProtocol', () => { const updateStateSpy = jest.spyOn(credentialProtocol, 'updateState') // mock resolved format call - mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptOffer).mockResolvedValue({ attachment: requestAttachment, format: new CredentialFormatSpec({ format: 'indy', @@ -429,7 +413,7 @@ describe('V1CredentialProtocol', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', @@ -459,7 +443,7 @@ describe('V1CredentialProtocol', () => { mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', @@ -499,7 +483,7 @@ describe('V1CredentialProtocol', () => { mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) const comment = 'credential response comment' - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(legacyIndyCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: new CredentialFormatSpec({ format: 'the-format', @@ -522,7 +506,7 @@ describe('V1CredentialProtocol', () => { '~please_ack': expect.any(Object), }) - expect(indyCredentialFormatService.acceptRequest).toHaveBeenCalledWith(agentContext, { + expect(legacyIndyCredentialFormatService.acceptRequest).toHaveBeenCalledWith(agentContext, { credentialRecord, requestAttachment, offerAttachment, @@ -561,7 +545,7 @@ describe('V1CredentialProtocol', () => { associatedRecordId: credentialRecord.id, }) - expect(indyCredentialFormatService.processCredential).toHaveBeenNthCalledWith(1, agentContext, { + expect(legacyIndyCredentialFormatService.processCredential).toHaveBeenNthCalledWith(1, agentContext, { attachment: credentialAttachment, credentialRecord, requestAttachment: expect.any(Attachment), @@ -840,7 +824,7 @@ describe('V1CredentialProtocol', () => { }) it('should call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(legacyIndyCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -858,7 +842,7 @@ describe('V1CredentialProtocol', () => { }) it('should not call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is false', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(legacyIndyCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -872,7 +856,7 @@ describe('V1CredentialProtocol', () => { }) it('deleteAssociatedCredentials should default to true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(legacyIndyCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -886,7 +870,7 @@ describe('V1CredentialProtocol', () => { ) }) it('deleteAssociatedDidCommMessages should default to true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(legacyIndyCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts index 947d63aaa4..95de9d3e27 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts @@ -1,27 +1,27 @@ +import type { LegacyIndyCredentialFormatService } from '../../../../formats/LegacyIndyCredentialFormatService' +import type { CredentialProtocolOptions, CredentialStateChangedEvent } from '@aries-framework/core' + import { CredentialRepository, DidCommMessageRepository, RoutingService, ConnectionService, Dispatcher, + EventEmitter, DidExchangeState, Attachment, AttachmentData, CredentialState, CredentialFormatSpec, CredentialExchangeRecord, - CredentialStateChangedEvent, CredentialEventTypes, - CreateCredentialOfferOptions, JsonTransformer, InboundMessageContext, - EventEmitter, } from '@aries-framework/core' -import { CreateCredentialProposalOptions } from '@aries-framework/core/src/modules/credentials/protocol/CredentialProtocolOptions' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '@aries-framework/core/tests/helpers' -import { Subject } from 'rxjs' -import { V1CredentialPreview, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' + +import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../core/tests/helpers' import { V1CredentialProtocol } from '../V1CredentialProtocol' +import { V1CredentialPreview, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' // Mock classes jest.mock('../../../repository/CredentialRepository') @@ -115,7 +115,9 @@ describe('V1CredentialProtocolProposeOffer', () => { }) describe('createProposal', () => { - const proposeOptions: CreateCredentialProposalOptions<[IndyCredentialFormatService]> = { + const proposeOptions: CredentialProtocolOptions.CreateCredentialProposalOptions< + [LegacyIndyCredentialFormatService] + > = { connectionRecord: connectionRecord, credentialFormats: { indy: { @@ -228,7 +230,7 @@ describe('V1CredentialProtocolProposeOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateCredentialOfferOptions<[IndyCredentialFormatService]> = { + const offerOptions: CredentialProtocolOptions.CreateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> = { comment: 'some comment', connectionRecord, credentialFormats: { diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index dc0818a14b..e7855202ad 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -1,23 +1,22 @@ -import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '../../../CredentialsApiOptions' - +import { + AcceptCredentialOfferOptions, + AcceptCredentialRequestOptions, + Agent, + AutoAcceptCredential, + CredentialEventTypes, + CredentialExchangeRecord, + CredentialState, + CredentialStateChangedEvent, +} from '@aries-framework/core' import { ReplaySubject, Subject } from 'rxjs' -import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { - prepareForIndyIssuance, - waitForCredentialRecordSubject, - getAgentOptions, -} from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { Agent } from '../../../../../agent/Agent' -import { CredentialEventTypes } from '../../../CredentialEvents' -import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { V1CredentialPreview } from '../messages/V1CredentialPreview' +import { SubjectInboundTransport, SubjectMessage } from '../../../../../../../tests/transport/SubjectInboundTransport' + +import { SubjectOutboundTransport } from '../../../../../../../tests/transport/SubjectOutboundTransport' +import { getAgentOptions, waitForCredentialRecordSubject } from '../../../../../../core/tests/helpers' +import testLogger from '../../../../../../core/tests/logger' +import { prepareForAnonCredsIssuance } from '../../../../../tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../messages' const faberAgentOptions = getAgentOptions('Faber connection-less Credentials V1', { endpoints: ['rxjs:faber'], @@ -57,8 +56,11 @@ describe('V1 Connectionless Credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age']) - credentialDefinitionId = definition.id + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age'], + issuerId: faberAgent.publicDid?.did as string, + }) + credentialDefinitionId = credentialDefinition.credentialDefinitionId faberReplay = new ReplaySubject() aliceReplay = new ReplaySubject() diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index 42da4ed4da..668bd427bb 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -1,44 +1,55 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { AcceptCredentialOfferOptions, AcceptCredentialProposalOptions } from '../../../CredentialsApiOptions' -import type { Schema } from 'indy-sdk' - -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { V1CredentialPreview } from '../messages/V1CredentialPreview' - -describe('credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let schema: Schema - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - 'x-ray': 'some x-ray', - profile_picture: 'profile picture', - }) - const newCredentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - 'x-ray': 'another x-ray value', - profile_picture: 'another profile picture', - }) +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' +import type { AcceptCredentialOfferOptions } from '@aries-framework/core' + +import { + AutoAcceptCredential, + CredentialState, + CredentialExchangeRecord, + JsonTransformer, + AriesFrameworkError, +} from '@aries-framework/core' + +import { waitForCredentialRecord } from '../../../../../../core/tests/helpers' +import testLogger from '../../../../../../core/tests/logger' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../messages' + +const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'some x-ray', + profile_picture: 'profile picture', +}) +const newCredentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + 'x-ray': 'another x-ray value', + profile_picture: 'another profile picture', +}) + +describe('V1 Credentials Auto Accept', () => { + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let credentialDefinitionId: string + let schemaId: string + let faberConnectionId: string + let aliceConnectionId: string describe('Auto accept on `always`', () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: always v1', - 'alice agent: always v1', - AutoAcceptCredential.Always - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'faber agent: always v1', + holderName: 'alice agent: always v1', + // Not needed for this test + verifierName: 'verifier agent: always v1', + autoAcceptCredentials: AutoAcceptCredential.Always, + })) }) afterAll(async () => { @@ -52,12 +63,12 @@ describe('credentials', () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', @@ -82,8 +93,8 @@ describe('credentials', () => { metadata: { data: { '_internal/indyCredential': { - schemaId: schema.id, - credentialDefinitionId: credDefId, + schemaId: schemaId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -93,14 +104,13 @@ describe('credentials', () => { test('Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { testLogger.test('Faber sends credential offer to Alice') - const schemaId = schema.id const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v1', @@ -124,7 +134,7 @@ describe('credentials', () => { '_internal/indyRequest': expect.any(Object), '_internal/indyCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId, }, }, }, @@ -147,11 +157,19 @@ describe('credentials', () => { describe('Auto accept on `contentApproved`', () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: contentApproved v1', - 'alice agent: contentApproved v1', - AutoAcceptCredential.ContentApproved - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'faber agent: contentApproved v1', + holderName: 'alice agent: contentApproved v1', + // Not needed for this test + verifierName: 'verifier agent: always v1', + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + })) }) afterAll(async () => { @@ -166,40 +184,34 @@ describe('credentials', () => { // ========================== test('Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { testLogger.test('Alice sends credential proposal to Faber') - const schemaId = schema.id - let faberCredentialExchangeRecord: CredentialExchangeRecord - let aliceCredentialExchangeRecord: CredentialExchangeRecord - - aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + let aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) - const options: AcceptCredentialProposalOptions = { + testLogger.test('Faber sends credential offer to Alice') + faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal({ credentialRecordId: faberCredentialExchangeRecord.id, comment: 'V1 Indy Offer', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, - } - testLogger.test('Faber sends credential offer to Alice') - options.credentialRecordId = faberCredentialExchangeRecord.id - faberCredentialExchangeRecord = await faberAgent.credentials.acceptProposal(options) + }) testLogger.test('Alice waits for credential from Faber') aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { @@ -222,7 +234,7 @@ describe('credentials', () => { '_internal/indyRequest': expect.any(Object), '_internal/indyCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -243,7 +255,7 @@ describe('credentials', () => { data: { '_internal/indyCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -253,24 +265,20 @@ describe('credentials', () => { test('Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { testLogger.test('Faber sends credential offer to Alice') - const schemaId = schema.id - let aliceCredentialExchangeRecord: CredentialExchangeRecord - let faberCredentialExchangeRecord: CredentialExchangeRecord - - faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ + let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v1', }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -284,7 +292,7 @@ describe('credentials', () => { expect(aliceCredentialExchangeRecord.getTags()).toEqual({ threadId: aliceCredentialExchangeRecord.threadId, state: aliceCredentialExchangeRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) @@ -316,7 +324,7 @@ describe('credentials', () => { '_internal/indyRequest': expect.any(Object), '_internal/indyCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -344,11 +352,11 @@ describe('credentials', () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v1', @@ -365,7 +373,7 @@ describe('credentials', () => { expect(aliceCredentialExchangeRecord.getTags()).toEqual({ threadId: aliceCredentialExchangeRecord.threadId, state: aliceCredentialExchangeRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) @@ -375,7 +383,7 @@ describe('credentials', () => { credentialFormats: { indy: { attributes: newCredentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', @@ -398,12 +406,12 @@ describe('credentials', () => { test('Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', @@ -419,7 +427,7 @@ describe('credentials', () => { credentialRecordId: faberCredentialExchangeRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -437,7 +445,7 @@ describe('credentials', () => { expect(record.getTags()).toEqual({ threadId: record.threadId, state: record.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts index 1d25498a3a..6846784265 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts @@ -1,12 +1,15 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' -import { JsonTransformer } from '../../../../../utils' -import { CredentialState } from '../../../models/CredentialState' -import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { + CredentialExchangeRecord, + CredentialState, + DidCommMessageRepository, + JsonTransformer, +} from '@aries-framework/core/src' +import { waitForCredentialRecord } from '@aries-framework/core/tests/helpers' +import testLogger from '@aries-framework/core/tests/logger' + +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' import { V1ProposeCredentialMessage, V1RequestCredentialMessage, @@ -15,19 +18,24 @@ import { V1CredentialPreview, } from '../messages' -describe('v1 credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let faberCredentialRecord: CredentialExchangeRecord +describe('V1 Credentials', () => { + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let credentialDefinitionId: string + let aliceConnectionId: string beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, aliceConnection } = await setupCredentialTests( - 'Faber Agent Credentials v1', - 'Alice Agent Credentials v1' - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Agent Credentials V1', + holderName: 'Alice Agent Credentials V1', + // Not needed for this test + verifierName: 'Verifier Agent Credentials V1', + })) }) afterAll(async () => { @@ -48,7 +56,7 @@ describe('v1 credentials', () => { testLogger.test('Alice sends (v1) credential proposal to Faber') const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { @@ -65,14 +73,14 @@ describe('v1 credentials', () => { }) expect(credentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', state: CredentialState.ProposalSent, threadId: expect.any(String), }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialRecord = await waitForCredentialRecord(faberAgent, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -83,14 +91,14 @@ describe('v1 credentials', () => { comment: 'V1 Indy Proposal', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) @@ -152,7 +160,7 @@ describe('v1 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', state: CredentialState.RequestSent, threadId: expect.any(String), diff --git a/packages/anoncreds/src/utils/__tests__/credentialPreviewAttributes.test.ts b/packages/anoncreds/src/utils/__tests__/credentialPreviewAttributes.test.ts new file mode 100644 index 0000000000..419f9af0b7 --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/credentialPreviewAttributes.test.ts @@ -0,0 +1,143 @@ +import { areCredentialPreviewAttributesEqual } from '../credentialPreviewAttributes' + +describe('areCredentialPreviewAttributesEqual', () => { + test('returns true if the attributes are equal', () => { + const firstAttributes = [ + { + name: 'firstName', + value: 'firstValue', + mimeType: 'text/grass', + }, + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'firstName', + value: 'firstValue', + mimeType: 'text/grass', + }, + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(true) + }) + + test('returns false if the attribute name and value are equal but the mime type is different', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/notGrass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) + + test('returns false if the attribute name and mime type are equal but the value is different', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'secondName', + value: 'thirdValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) + + test('returns false if the value and mime type are equal but the name is different', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'thirdName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) + + test('returns false if the length of the attributes does not match', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'thirdName', + value: 'secondValue', + mimeType: 'text/grass', + }, + { + name: 'fourthName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) + + test('returns false if duplicate key names exist', () => { + const firstAttributes = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + const secondAttribute = [ + { + name: 'secondName', + value: 'secondValue', + mimeType: 'text/grass', + }, + ] + + expect(areCredentialPreviewAttributesEqual(firstAttributes, secondAttribute)).toBe(false) + }) +}) diff --git a/packages/anoncreds/src/utils/composeAutoAccept.ts b/packages/anoncreds/src/utils/composeAutoAccept.ts new file mode 100644 index 0000000000..0d874154d2 --- /dev/null +++ b/packages/anoncreds/src/utils/composeAutoAccept.ts @@ -0,0 +1,21 @@ +import { AutoAcceptCredential, AutoAcceptProof } from '@aries-framework/core' + +/** + * Returns the credential auto accept config based on priority: + * - The record config takes first priority + * - Otherwise the agent config + * - Otherwise {@link AutoAcceptCredential.Never} is returned + */ +export function composeCredentialAutoAccept(recordConfig?: AutoAcceptCredential, agentConfig?: AutoAcceptCredential) { + return recordConfig ?? agentConfig ?? AutoAcceptCredential.Never +} + +/** + * Returns the proof auto accept config based on priority: + * - The record config takes first priority + * - Otherwise the agent config + * - Otherwise {@link AutoAcceptProof.Never} is returned + */ +export function composeProofAutoAccept(recordConfig?: AutoAcceptProof, agentConfig?: AutoAcceptProof) { + return recordConfig ?? agentConfig ?? AutoAcceptProof.Never +} diff --git a/packages/anoncreds/src/utils/credentialPreviewAttributes.ts b/packages/anoncreds/src/utils/credentialPreviewAttributes.ts new file mode 100644 index 0000000000..686e07ac80 --- /dev/null +++ b/packages/anoncreds/src/utils/credentialPreviewAttributes.ts @@ -0,0 +1,27 @@ +import type { CredentialPreviewAttributeOptions } from '@aries-framework/core' + +export function areCredentialPreviewAttributesEqual( + firstAttributes: CredentialPreviewAttributeOptions[], + secondAttributes: CredentialPreviewAttributeOptions[] +) { + if (firstAttributes.length !== secondAttributes.length) return false + + const secondAttributeMap = secondAttributes.reduce>( + (attributeMap, attribute) => ({ ...attributeMap, [attribute.name]: attribute }), + {} + ) + + // check if no duplicate keys exist + if (new Set(firstAttributes.map((attribute) => attribute.name)).size !== firstAttributes.length) return false + if (new Set(secondAttributes.map((attribute) => attribute.name)).size !== secondAttributes.length) return false + + for (const firstAttribute of firstAttributes) { + const secondAttribute = secondAttributeMap[firstAttribute.name] + + if (!secondAttribute) return false + if (firstAttribute.value !== secondAttribute.value) return false + if (firstAttribute.mimeType !== secondAttribute.mimeType) return false + } + + return true +} diff --git a/packages/anoncreds/src/utils/index.ts b/packages/anoncreds/src/utils/index.ts index a140e13cfb..6c9cbde0c1 100644 --- a/packages/anoncreds/src/utils/index.ts +++ b/packages/anoncreds/src/utils/index.ts @@ -6,3 +6,5 @@ export { downloadTailsFile } from './tails' export { assertRevocationInterval } from './revocationInterval' export { encodeCredentialValue, checkValidCredentialValueEncoding } from './credential' export { IsMap } from './isMap' +export { composeCredentialAutoAccept, composeProofAutoAccept } from './composeAutoAccept' +export { areCredentialPreviewAttributesEqual } from './credentialPreviewAttributes' diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 105153cd56..0778a37bea 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -307,7 +307,7 @@ export async function setupAnonCredsTests({ verifierAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await verifierAgent.initialize() - const { credentialDefinition } = await prepareForAnonCredsIssuance(issuerAgent, { + const { credentialDefinition, schema } = await prepareForAnonCredsIssuance(issuerAgent, { attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], // TODO: replace with more dynamic / generic value We should create a did using the dids module // and use that probably @@ -346,6 +346,7 @@ export async function setupAnonCredsTests({ verifierReplay, credentialDefinitionId: credentialDefinition.credentialDefinitionId, + schemaId: schema.schemaId, issuerHolderConnectionId: issuerHolderConnection.id, holderIssuerConnectionId: holderIssuerConnection.id, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 5bc03b0d8a..10fa76550e 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -68,13 +68,13 @@ export type { Constructor } from './utils/mixins' export * from './agent/Events' export * from './crypto/' -export { encodeAttachment } from './utils/attachment' +// TODO: clean up util exports +export { encodeAttachment, isLinkedAttachment } from './utils/attachment' export { Hasher } from './utils/Hasher' export { MessageValidator } from './utils/MessageValidator' export { LinkedAttachment, LinkedAttachmentOptions } from './utils/LinkedAttachment' import { parseInvitationUrl } from './utils/parseInvitation' import { uuid } from './utils/uuid' - const utils = { uuid, parseInvitationUrl, diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 47e7ccda00..482c688d92 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -10,14 +10,13 @@ import { Protocol } from '../../agent/models' import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' import { RevocationNotificationService } from './protocol/revocation-notification/services' -import { V1CredentialProtocol } from './protocol/v1' import { V2CredentialProtocol } from './protocol/v2' import { CredentialRepository } from './repository' /** * Default credentialProtocols that will be registered if the `credentialProtocols` property is not configured. */ -export type DefaultCredentialProtocols = [V1CredentialProtocol, V2CredentialProtocol<[]>] +export type DefaultCredentialProtocols = [V2CredentialProtocol<[]>] // CredentialsModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. export type CredentialsModuleOptions = Optional< diff --git a/packages/core/src/modules/credentials/protocol/index.ts b/packages/core/src/modules/credentials/protocol/index.ts index 394b0e5b41..cb3d5c3b51 100644 --- a/packages/core/src/modules/credentials/protocol/index.ts +++ b/packages/core/src/modules/credentials/protocol/index.ts @@ -3,4 +3,9 @@ export * from './revocation-notification' import * as CredentialProtocolOptions from './CredentialProtocolOptions' export { CredentialProtocol } from './CredentialProtocol' +// NOTE: ideally we don't export the BaseCredentialProtocol, but as the V1CredentialProtocol is defined in the +// anoncreds package, we need to export it. We should at some point look at creating a core package which can be used for +// sharing internal types, and when you want to build you own modules, and an agent package, which is the one you use when +// consuming the framework +export { BaseCredentialProtocol } from './BaseCredentialProtocol' export { CredentialProtocolOptions } diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts index 2a287c1fac..2ee2a64a3b 100644 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts @@ -3,7 +3,6 @@ import type { Wallet, DidRecord, RecordSavedEvent } from '@aries-framework/core' import { DidRepository, - IndyPoolService, SigningProviderRegistry, JsonTransformer, DidDocumentRole, @@ -22,10 +21,10 @@ import { IndySdkSovDidRegistrar } from '../IndySdkSovDidRegistrar' jest.mock('../../ledger/IndySdkPoolService') const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock -const indyPoolServiceMock = new IndySdkPoolServiceMock() +const indySdkPoolServiceMock = new IndySdkPoolServiceMock() -mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ - config: { id: 'pool1', indyNamespace: 'pool1' }, +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { indyNamespace: 'pool1' }, } as IndySdkPool) const agentConfig = getAgentConfig('IndySdkSovDidRegistrar') @@ -39,7 +38,7 @@ const agentContext = getAgentContext({ wallet, registerInstances: [ [DidRepository, didRepository], - [IndyPoolService, indyPoolServiceMock], + [IndySdkPoolService, indySdkPoolServiceMock], [IndySdkSymbol, indySdk], ], agentConfig, diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts index 0f67c0bf76..71a969f74e 100644 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts @@ -2,7 +2,7 @@ import type { IndySdkPool } from '../../ledger' import type { IndyEndpointAttrib } from '../didSovUtil' import type { GetNymResponse } from 'indy-sdk' -import { IndyPoolService, SigningProviderRegistry, JsonTransformer } from '@aries-framework/core' +import { SigningProviderRegistry, JsonTransformer } from '@aries-framework/core' import { parseDid } from '@aries-framework/core/src/modules/dids/domain/parse' import { mockFunction, getAgentConfig, getAgentContext } from '@aries-framework/core/tests/helpers' import indySdk from 'indy-sdk' @@ -17,10 +17,10 @@ import didSovWJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didSovWJz9mHyW9B jest.mock('../../ledger/IndySdkPoolService') const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock -const indyPoolServiceMock = new IndySdkPoolServiceMock() +const indySdkPoolServiceMock = new IndySdkPoolServiceMock() -mockFunction(indyPoolServiceMock.getPoolForNamespace).mockReturnValue({ - config: { id: 'pool1', indyNamespace: 'pool1' }, +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { indyNamespace: 'pool1' }, } as IndySdkPool) const agentConfig = getAgentConfig('IndySdkSovDidResolver') @@ -31,7 +31,7 @@ const agentContext = getAgentContext({ wallet, agentConfig, registerInstances: [ - [IndyPoolService, indyPoolServiceMock], + [IndySdkPoolService, indySdkPoolServiceMock], [IndySdkSymbol, indySdk], ], }) diff --git a/yarn.lock b/yarn.lock index 0a86c85b57..e63f20b24a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10029,7 +10029,7 @@ rxjs@^6.6.0: dependencies: tslib "^1.9.0" -rxjs@^7.2.0: +rxjs@^7.2.0, rxjs@^7.8.0: version "7.8.0" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-7.8.0.tgz#90a938862a82888ff4c7359811a595e14e1e09a4" integrity sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg== From a7efd84c5df3f74b3656edc12fa715c2c521b146 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 14 Feb 2023 09:19:07 +0100 Subject: [PATCH 07/22] temp Signed-off-by: Timo Glastra --- package.json | 2 + .../LegacyIndyCredentialFormatService.ts | 16 +- .../src/formats/LegacyIndyProofFormat.ts | 8 +- .../formats/LegacyIndyProofFormatService.ts | 8 +- packages/anoncreds/src/index.ts | 1 + .../credentials/v1/V1CredentialProtocol.ts | 3 +- .../v1-connectionless-credentials.e2e.test.ts | 11 +- .../v1-credentials-auto-accept.e2e.test.ts | 2 - .../v1/__tests__/v1-credentials.e2e.test.ts | 9 +- .../errors/V1CredentialProblemReportError.ts | 20 + .../protocols/credentials/v1/errors/index.ts | 1 + .../v1/handlers/V1CredentialAckHandler.ts | 2 +- .../V1CredentialProblemReportHandler.ts | 2 +- .../v1/handlers/V1IssueCredentialHandler.ts | 7 +- .../v1/handlers/V1OfferCredentialHandler.ts | 5 - .../v1/handlers/V1RequestCredentialHandler.ts | 14 +- .../v1/messages/V1ProposeCredentialMessage.ts | 18 +- packages/anoncreds/src/protocols/index.ts | 2 + .../protocols/proofs}/v1/V1ProofProtocol.ts | 164 +++--- .../v1/__tests__/V1ProofProtocol.test.ts | 0 .../v1-connectionless-proofs.e2e.test.ts | 0 .../v1-indy-proof-negotiation.test.ts} | 0 .../v1-indy-proof-presentation.test.e2e.ts} | 0 .../v1-indy-proof-proposal.test.e2e.ts} | 0 .../v1-indy-proof-request.e2e.test.ts | 0 .../v1/__tests__/v1-indy-proofs.e2e.test.ts | 0 .../v1-proofs-auto-accept.e2e.test.ts | 0 .../V1PresentationProblemReportError.ts | 8 +- .../src/protocols/proofs}/v1/errors/index.ts | 0 .../v1/handlers/V1PresentationAckHandler.ts | 2 +- .../v1/handlers/V1PresentationHandler.ts | 7 +- .../V1PresentationProblemReportHandler.ts | 2 +- .../handlers/V1ProposePresentationHandler.ts | 6 +- .../handlers/V1RequestPresentationHandler.ts | 15 +- .../protocols/proofs}/v1/handlers/index.ts | 0 .../src/protocols/proofs}/v1/index.ts | 0 .../v1/messages/V1PresentationAckMessage.ts | 5 +- .../v1/messages/V1PresentationMessage.ts | 11 +- .../V1PresentationProblemReportMessage.ts | 5 +- .../messages/V1ProposePresentationMessage.ts | 3 +- .../messages/V1RequestPresentationMessage.ts | 11 +- .../protocols/proofs}/v1/messages/index.ts | 0 .../v1/models/V1PresentationPreview.ts | 19 +- .../src/protocols/proofs}/v1/models/index.ts | 0 .../__tests__/legacyIndyIdentifiers.test.ts | 34 ++ packages/anoncreds/src/utils/index.ts | 6 + .../src/utils/legacyIndyIdentifiers.ts | 5 + .../anoncreds/tests/legacyAnonCredsSetup.ts | 142 +++-- packages/core/package.json | 1 - packages/core/src/agent/Agent.ts | 11 + .../src/crypto/__tests__/JwsService.test.ts | 10 +- .../errors/CredentialProblemReportError.ts | 23 - .../src/modules/credentials/errors/index.ts | 2 - .../JsonLdCredentialFormatService.test.ts | 70 +-- .../core/src/modules/credentials/index.ts | 1 - .../CredentialProblemReportReason.ts | 0 .../src/modules/credentials/models/index.ts | 1 + .../protocol/v2/V2CredentialProtocol.ts | 3 +- .../V2CredentialProtocolCred.test.ts | 2 +- .../errors/V2CredentialProblemReportError.ts | 23 + .../credentials/protocol/v2/errors/index.ts | 1 + .../modules/credentials/protocol/v2/index.ts | 1 + .../core/src/modules/dids/DidsModuleConfig.ts | 27 +- .../oob/__tests__/OutOfBandService.test.ts | 22 +- .../proofs/__tests__/ProofsModule.test.ts | 3 +- .../src/modules/proofs/__tests__/fixtures.ts | 47 -- .../errors/InvalidEncodedValueError.ts | 3 - .../errors/MissingIndyProofMessageError.ts | 3 - .../formats/indy/__tests__/util.test.ts | 541 ------------------ .../src/modules/proofs/formats/indy/util.ts | 266 --------- .../core/src/modules/proofs/protocol/index.ts | 8 +- .../v2/__tests__/v2-indy-proofs.e2e.test.ts | 312 +++++----- .../services/__tests__/RoutingService.test.ts | 17 +- .../DidCommMessageRepository.test.ts | 10 +- .../src/storage/__tests__/Repository.test.ts | 9 +- .../core/src/utils/__tests__/regex.test.ts | 29 - packages/core/src/utils/index.ts | 1 - packages/core/src/utils/regex.ts | 4 - packages/core/src/utils/transformers.ts | 39 -- packages/core/src/utils/type.ts | 4 - .../core/src/wallet/util/assertIndyWallet.ts | 12 - packages/core/tests/oob.test.ts | 79 ++- .../core/tests/proofs-sub-protocol.test.ts | 144 +++-- packages/indy-sdk/package.json | 1 + packages/indy-sdk/src/IndySdkModule.ts | 3 + .../__tests__/IndySdkSovDidRegistrar.test.ts | 2 +- .../__tests__/IndySdkSovDidResolver.test.ts | 4 +- packages/indy-sdk/src/ledger/IndySdkPool.ts | 2 +- .../indy-sdk/src/ledger/IndySdkPoolService.ts | 2 +- packages/indy-sdk/src/wallet/IndySdkWallet.ts | 2 +- packages/indy-sdk/tests/setupIndySdkModule.ts | 25 + .../tests/sov-did-registrar.e2e.test.ts | 19 +- .../tests/sov-did-resolver.e2e.test.ts | 27 +- packages/indy-vdr/src/IndyVdrModule.ts | 12 +- packages/indy-vdr/src/pool/IndyVdrPool.ts | 7 +- .../indy-vdr/src/pool/IndyVdrPoolService.ts | 17 +- .../indy-vdr-anoncreds-registry.e2e.test.ts | 1 - .../tests/indy-vdr-did-resolver.e2e.test.ts | 6 +- .../indy-vdr/tests/indy-vdr-pool.e2e.test.ts | 6 +- yarn.lock | 39 +- 100 files changed, 839 insertions(+), 1641 deletions(-) create mode 100644 packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts create mode 100644 packages/anoncreds/src/protocols/credentials/v1/errors/index.ts create mode 100644 packages/anoncreds/src/protocols/index.ts rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/V1ProofProtocol.ts (88%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/__tests__/V1ProofProtocol.test.ts (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/__tests__/v1-connectionless-proofs.e2e.test.ts (100%) rename packages/{core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts => anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts} (100%) rename packages/{core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.e2e.ts => anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts} (100%) rename packages/{core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.e2e.ts => anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts} (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/__tests__/v1-indy-proof-request.e2e.test.ts (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/__tests__/v1-indy-proofs.e2e.test.ts (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/errors/V1PresentationProblemReportError.ts (62%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/errors/index.ts (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/handlers/V1PresentationAckHandler.ts (93%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/handlers/V1PresentationHandler.ts (90%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/handlers/V1PresentationProblemReportHandler.ts (94%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/handlers/V1ProposePresentationHandler.ts (86%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/handlers/V1RequestPresentationHandler.ts (86%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/handlers/index.ts (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/index.ts (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/messages/V1PresentationAckMessage.ts (64%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/messages/V1PresentationMessage.ts (84%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/messages/V1PresentationProblemReportMessage.ts (72%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/messages/V1ProposePresentationMessage.ts (91%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/messages/V1RequestPresentationMessage.ts (82%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/messages/index.ts (100%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/models/V1PresentationPreview.ts (87%) rename packages/{core/src/modules/proofs/protocol => anoncreds/src/protocols/proofs}/v1/models/index.ts (100%) create mode 100644 packages/anoncreds/src/utils/__tests__/legacyIndyIdentifiers.test.ts create mode 100644 packages/anoncreds/src/utils/legacyIndyIdentifiers.ts delete mode 100644 packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts delete mode 100644 packages/core/src/modules/credentials/errors/index.ts rename packages/core/src/modules/credentials/formats/{ => jsonld}/__tests__/JsonLdCredentialFormatService.test.ts (90%) rename packages/core/src/modules/credentials/{errors => models}/CredentialProblemReportReason.ts (100%) create mode 100644 packages/core/src/modules/credentials/protocol/v2/errors/V2CredentialProblemReportError.ts create mode 100644 packages/core/src/modules/credentials/protocol/v2/errors/index.ts delete mode 100644 packages/core/src/modules/proofs/__tests__/fixtures.ts delete mode 100644 packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts delete mode 100644 packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts delete mode 100644 packages/core/src/modules/proofs/formats/indy/util.ts delete mode 100644 packages/core/src/utils/__tests__/regex.test.ts delete mode 100644 packages/core/src/utils/regex.ts delete mode 100644 packages/core/src/wallet/util/assertIndyWallet.ts create mode 100644 packages/indy-sdk/tests/setupIndySdkModule.ts diff --git a/package.json b/package.json index c56b68e398..8656ccf5af 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "conventional-recommended-bump": "^6.1.0", "cors": "^2.8.5", "dotenv": "^10.0.0", + "encryption-envelope-js": "^1.2.5", "eslint": "^7.28.0", "eslint-config-prettier": "^8.3.0", "eslint-import-resolver-typescript": "^2.4.0", @@ -50,6 +51,7 @@ "indy-sdk": "^1.16.0-dev-1636", "jest": "^27.0.4", "lerna": "^4.0.0", + "libsodium-wrappers": "^0.7.10", "prettier": "^2.3.1", "rxjs": "^7.8.0", "ts-jest": "^27.0.3", diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts index 93e2151870..dd50a79e44 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -7,7 +7,7 @@ import type { } from '../models' import type { AnonCredsIssuerService, AnonCredsHolderService, GetRevocationRegistryDefinitionReturn } from '../services' import type { AnonCredsCredentialMetadata } from '../utils/metadata' -import type { +import { CredentialFormatService, AgentContext, CredentialFormatCreateProposalOptions, @@ -27,6 +27,7 @@ import type { CredentialExchangeRecord, CredentialPreviewAttributeOptions, LinkedAttachment, + ProblemReportError, } from '@aries-framework/core' import { @@ -36,7 +37,6 @@ import { Attachment, JsonEncoder, utils, - CredentialProblemReportError, CredentialProblemReportReason, JsonTransformer, } from '@aries-framework/core' @@ -205,7 +205,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic const credOffer = attachment.getDataAsJson() if (!credOffer.schema_id || !credOffer.cred_def_id) { - throw new CredentialProblemReportError('Invalid credential offer', { + throw new ProblemReportError('Invalid credential offer', { problemCode: CredentialProblemReportReason.IssuanceAbandoned, }) } @@ -289,9 +289,8 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic // Assert credential attributes const credentialAttributes = credentialRecord.credentialAttributes if (!credentialAttributes) { - throw new CredentialProblemReportError( - `Missing required credential attribute values on credential record with id ${credentialRecord.id}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + throw new AriesFrameworkError( + `Missing required credential attribute values on credential record with id ${credentialRecord.id}` ) } @@ -344,9 +343,8 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic agentContext.dependencyManager.resolve(AnonCredsHolderServiceSymbol) if (!credentialRequestMetadata) { - throw new CredentialProblemReportError( - `Missing required request metadata for credential with id ${credentialRecord.id}`, - { problemCode: CredentialProblemReportReason.IssuanceAbandoned } + throw new AriesFrameworkError( + `Missing required request metadata for credential exchange with thread id with id ${credentialRecord.id}` ) } diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts index c2dfc2cf0d..a586e77b10 100644 --- a/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormat.ts @@ -7,6 +7,9 @@ import type { import type { AnonCredsProof, AnonCredsProofRequest, AnonCredsSelectedCredentials } from '../models' import type { ProofFormat } from '@aries-framework/core' +// TODO: Custom restrictions to remove `_id` from restrictions? +export type LegacyIndyProofRequest = AnonCredsProofRequest + export interface LegacyIndyProofFormat extends ProofFormat { formatKey: 'indy' @@ -30,9 +33,8 @@ export interface LegacyIndyProofFormat extends ProofFormat { } formatData: { - // TODO: Custom restrictions to remove `_id` from restrictions? - proposal: AnonCredsProofRequest - request: AnonCredsProofRequest + proposal: LegacyIndyProofRequest + request: LegacyIndyProofRequest presentation: AnonCredsProof } } diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts index b9a51d33c1..80c8daece5 100644 --- a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -38,6 +38,7 @@ import type { ProofFormatSelectCredentialsForRequestReturn, ProofFormatAutoRespondProposalOptions, ProofFormatAutoRespondRequestOptions, + ProofFormatAutoRespondPresentationOptions, } from '@aries-framework/core' import { @@ -318,7 +319,12 @@ export class LegacyIndyProofFormatService implements ProofFormatService { + public async shouldAutoRespondToPresentation( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _agentContext: AgentContext, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + _options: ProofFormatAutoRespondPresentationOptions + ): Promise { // The presentation is already verified in processPresentation, so we can just return true here. // It's only an ack, so it's just that we received the presentation. return true diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts index 46edc1fbcd..11e113699c 100644 --- a/packages/anoncreds/src/index.ts +++ b/packages/anoncreds/src/index.ts @@ -3,6 +3,7 @@ export * from './services' export * from './error' export * from './repository' export * from './formats' +export * from './protocols' export { AnonCredsModule } from './AnonCredsModule' export { AnonCredsModuleConfig, AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' diff --git a/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts index 1b10e6eba4..428f512cf3 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts @@ -57,8 +57,6 @@ import { } from './messages' export interface V1CredentialProtocolConfig { - // indyCredentialFormat must be a service that implements the `IndyCredentialFormat` interface, however it doesn't - // have to be the IndyCredentialFormatService implementation per se. indyCredentialFormat: LegacyIndyCredentialFormatService } @@ -71,6 +69,7 @@ export class V1CredentialProtocol public constructor({ indyCredentialFormat }: V1CredentialProtocolConfig) { super() + // TODO: just create a new instance of LegacyIndyCredentialFormatService here so it makes the setup easier this.indyCredentialFormat = indyCredentialFormat } diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index e7855202ad..dc4ea3c4bd 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -1,17 +1,20 @@ -import { +import type { SubjectMessage } from '../../../../../../../tests/transport/SubjectInboundTransport' +import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions, + CredentialStateChangedEvent, +} from '@aries-framework/core' + +import { Agent, AutoAcceptCredential, CredentialEventTypes, CredentialExchangeRecord, CredentialState, - CredentialStateChangedEvent, } from '@aries-framework/core' import { ReplaySubject, Subject } from 'rxjs' -import { SubjectInboundTransport, SubjectMessage } from '../../../../../../../tests/transport/SubjectInboundTransport' - +import { SubjectInboundTransport } from '../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../tests/transport/SubjectOutboundTransport' import { getAgentOptions, waitForCredentialRecordSubject } from '../../../../../../core/tests/helpers' import testLogger from '../../../../../../core/tests/logger' diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index 668bd427bb..79670842a2 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -46,8 +46,6 @@ describe('V1 Credentials Auto Accept', () => { } = await setupAnonCredsTests({ issuerName: 'faber agent: always v1', holderName: 'alice agent: always v1', - // Not needed for this test - verifierName: 'verifier agent: always v1', autoAcceptCredentials: AutoAcceptCredential.Always, })) }) diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts index 6846784265..78a6f2f852 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials.e2e.test.ts @@ -5,10 +5,10 @@ import { CredentialState, DidCommMessageRepository, JsonTransformer, -} from '@aries-framework/core/src' -import { waitForCredentialRecord } from '@aries-framework/core/tests/helpers' -import testLogger from '@aries-framework/core/tests/logger' +} from '@aries-framework/core' +import { waitForCredentialRecord } from '../../../../../../core/tests/helpers' +import testLogger from '../../../../../../core/tests/logger' import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' import { V1ProposeCredentialMessage, @@ -33,8 +33,7 @@ describe('V1 Credentials', () => { } = await setupAnonCredsTests({ issuerName: 'Faber Agent Credentials V1', holderName: 'Alice Agent Credentials V1', - // Not needed for this test - verifierName: 'Verifier Agent Credentials V1', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], })) }) diff --git a/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts b/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts new file mode 100644 index 0000000000..6d22aff7bd --- /dev/null +++ b/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts @@ -0,0 +1,20 @@ +import { ProblemReportErrorOptions, CredentialProblemReportReason, ProblemReportError } from '@aries-framework/core' +import { V1CredentialProblemReportMessage } from '../messages' + +export interface V1CredentialProblemReportErrorOptions extends ProblemReportErrorOptions { + problemCode: CredentialProblemReportReason +} + +export class V1CredentialProblemReportError extends ProblemReportError { + public problemReport: V1CredentialProblemReportMessage + + public constructor(message: string, { problemCode }: V1CredentialProblemReportErrorOptions) { + super(message, { problemCode }) + this.problemReport = new V1CredentialProblemReportMessage({ + description: { + en: message, + code: problemCode, + }, + }) + } +} diff --git a/packages/anoncreds/src/protocols/credentials/v1/errors/index.ts b/packages/anoncreds/src/protocols/credentials/v1/errors/index.ts new file mode 100644 index 0000000000..5d2b6fc15e --- /dev/null +++ b/packages/anoncreds/src/protocols/credentials/v1/errors/index.ts @@ -0,0 +1 @@ +export { V1CredentialProblemReportError, V1CredentialProblemReportErrorOptions } from './V1CredentialProblemReportError' diff --git a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts index 0a79843374..b4d3384ed9 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts @@ -1,7 +1,7 @@ import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1CredentialAckMessage } from '../messages' -import { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' export class V1CredentialAckHandler implements MessageHandler { private credentialProtocol: V1CredentialProtocol diff --git a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts index cfdb8be704..b2e599577d 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts @@ -1,7 +1,7 @@ import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1CredentialProblemReportMessage } from '../messages' -import { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' export class V1CredentialProblemReportHandler implements MessageHandler { private credentialProtocol: V1CredentialProtocol diff --git a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts index 82e7dea44a..e828fb2258 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts @@ -1,9 +1,8 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, CredentialExchangeRecord } from '@aries-framework/core' + +import { DidCommMessageRepository, OutboundMessageContext } from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' -import { DidCommMessageRepository } from '../../../../../storage' import { V1IssueCredentialMessage, V1RequestCredentialMessage } from '../messages' export class V1IssueCredentialHandler implements MessageHandler { diff --git a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts index fb5757587a..8d2d847e96 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts @@ -57,11 +57,6 @@ export class V1OfferCredentialHandler implements MessageHandler { const { message } = await this.credentialProtocol.acceptOffer(messageContext.agentContext, { credentialRecord, - credentialFormats: { - indy: { - holderDid: ourService.recipientKeys[0], - }, - }, }) // Set and save ~service decorator to record (to remember our verkey) diff --git a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts index f52df4abb0..00154fc8a4 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.ts @@ -1,13 +1,9 @@ -import { - CredentialExchangeRecord, - DidCommMessageRepository, - DidCommMessageRole, - MessageHandler, - MessageHandlerInboundMessage, - OutboundMessageContext, -} from '@aries-framework/core' +import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { CredentialExchangeRecord, MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' + +import { DidCommMessageRepository, DidCommMessageRole, OutboundMessageContext } from '@aries-framework/core' + import { V1RequestCredentialMessage } from '../messages' -import { V1CredentialProtocol } from '../V1CredentialProtocol' export class V1RequestCredentialHandler implements MessageHandler { private credentialProtocol: V1CredentialProtocol diff --git a/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts index 7f6aff1f35..a36db35b37 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts @@ -1,9 +1,13 @@ import { AgentMessage, Attachment, IsValidMessageType, parseMessageType } from '@aries-framework/core' -// FIXME -import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '@aries-framework/core/src/utils' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' +import { + legacyIndyCredentialDefinitionIdRegex, + legacyIndyDidRegex, + legacyIndySchemaIdRegex, + legacyIndySchemaVersionRegex, +} from '../../../../utils' import { V1CredentialPreview } from './V1CredentialPreview' export interface V1ProposeCredentialMessageOptions { @@ -70,7 +74,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'schema_issuer_did' }) @IsString() @IsOptional() - @Matches(indyDidRegex) + @Matches(legacyIndyDidRegex) public schemaIssuerDid?: string /** @@ -79,7 +83,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'schema_id' }) @IsString() @IsOptional() - @Matches(schemaIdRegex) + @Matches(legacyIndySchemaIdRegex) public schemaId?: string /** @@ -96,7 +100,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'schema_version' }) @IsString() @IsOptional() - @Matches(schemaVersionRegex, { + @Matches(legacyIndySchemaVersionRegex, { message: 'Version must be X.X or X.X.X', }) public schemaVersion?: string @@ -107,7 +111,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'cred_def_id' }) @IsString() @IsOptional() - @Matches(credDefIdRegex) + @Matches(legacyIndyCredentialDefinitionIdRegex) public credentialDefinitionId?: string /** @@ -116,6 +120,6 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'issuer_did' }) @IsString() @IsOptional() - @Matches(indyDidRegex) + @Matches(legacyIndyDidRegex) public issuerDid?: string } diff --git a/packages/anoncreds/src/protocols/index.ts b/packages/anoncreds/src/protocols/index.ts new file mode 100644 index 0000000000..d5a3d13f6c --- /dev/null +++ b/packages/anoncreds/src/protocols/index.ts @@ -0,0 +1,2 @@ +export * from './credentials/v1' +export * from './proofs/v1' diff --git a/packages/core/src/modules/proofs/protocol/v1/V1ProofProtocol.ts b/packages/anoncreds/src/protocols/proofs/v1/V1ProofProtocol.ts similarity index 88% rename from packages/core/src/modules/proofs/protocol/v1/V1ProofProtocol.ts rename to packages/anoncreds/src/protocols/proofs/v1/V1ProofProtocol.ts index 5461aa8ebc..5e27debbfb 100644 --- a/packages/core/src/modules/proofs/protocol/v1/V1ProofProtocol.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/V1ProofProtocol.ts @@ -1,49 +1,39 @@ -import type { AgentContext } from '../../../../agent' -import type { AgentMessage } from '../../../../agent/AgentMessage' -import type { FeatureRegistry } from '../../../../agent/FeatureRegistry' -import type { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' -import type { DependencyManager } from '../../../../plugins' -import type { ProblemReportMessage } from '../../../problem-reports' -import type { ProofFormatService } from '../../formats' -import type { ProofFormat } from '../../formats/ProofFormat' -import type { IndyProofFormat } from '../../formats/indy/IndyProofFormat' -import type { ProofProtocol } from '../ProofProtocol' +import type { LegacyIndyProofFormatService } from '../../../formats' import type { - AcceptPresentationOptions, - AcceptProofProposalOptions, - AcceptProofRequestOptions, - CreateProofProblemReportOptions, - CreateProofProposalOptions, - CreateProofRequestOptions, - GetCredentialsForRequestOptions, - GetCredentialsForRequestReturn, + ProofProtocol, + DependencyManager, + FeatureRegistry, + AgentContext, + ProofProtocolOptions, + InboundMessageContext, + AgentMessage, + ProblemReportMessage, GetProofFormatDataReturn, - NegotiateProofProposalOptions, - NegotiateProofRequestOptions, - ProofProtocolMsgReturnType, - SelectCredentialsForRequestOptions, - SelectCredentialsForRequestReturn, -} from '../ProofProtocolOptions' - -import { Protocol } from '../../../../agent/models' -import { Attachment } from '../../../../decorators/attachment/Attachment' -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../storage' -import { JsonEncoder } from '../../../../utils' -import { JsonTransformer } from '../../../../utils/JsonTransformer' -import { MessageValidator } from '../../../../utils/MessageValidator' -import { uuid } from '../../../../utils/uuid' -import { AckStatus } from '../../../common/messages/AckMessage' -import { ConnectionService } from '../../../connections' -import { ProofsModuleConfig } from '../../ProofsModuleConfig' -import { PresentationProblemReportReason } from '../../errors/PresentationProblemReportReason' -import { createRequestFromPreview } from '../../formats/indy/util' -import { AutoAcceptProof } from '../../models' -import { ProofState } from '../../models/ProofState' -import { ProofRepository } from '../../repository' -import { ProofExchangeRecord } from '../../repository/ProofExchangeRecord' -import { composeAutoAccept } from '../../utils/composeAutoAccept' -import { BaseProofProtocol } from '../BaseProofProtocol' + ProofFormat, +} from '@aries-framework/core' + +import { + BaseProofProtocol, + Protocol, + ProofRepository, + DidCommMessageRepository, + AriesFrameworkError, + MessageValidator, + ProofExchangeRecord, + ProofState, + DidCommMessageRole, + ConnectionService, + Attachment, + JsonTransformer, + PresentationProblemReportReason, + AckStatus, + ProofsModuleConfig, + AutoAcceptProof, + JsonEncoder, + utils, +} from '@aries-framework/core' + +import { composeProofAutoAccept, createRequestFromPreview } from '../../../utils' import { V1PresentationProblemReportError } from './errors' import { @@ -64,20 +54,17 @@ import { import { V1PresentationProblemReportMessage } from './messages/V1PresentationProblemReportMessage' import { V1PresentationPreview } from './models/V1PresentationPreview' -type IndyProofFormatServiceLike = ProofFormatService - export interface V1ProofProtocolConfig { - // indyCredentialFormat must be a service that implements the `IndyProofFormat` interface, however it doesn't - // have to be the IndyProofFormatService implementation per se. - indyProofFormat: ProofFormatService + indyProofFormat: LegacyIndyProofFormatService } -export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<[IndyProofFormatServiceLike]> { - private indyProofFormat: ProofFormatService +export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol<[LegacyIndyProofFormatService]> { + private indyProofFormat: LegacyIndyProofFormatService public constructor({ indyProofFormat }: V1ProofProtocolConfig) { super() + // TODO: just create a new instance of LegacyIndyProofFormatService here so it makes the setup easier this.indyProofFormat = indyProofFormat } @@ -116,8 +103,8 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< comment, parentThreadId, autoAcceptProof, - }: CreateProofProposalOptions<[IndyProofFormatServiceLike]> - ): Promise> { + }: ProofProtocolOptions.CreateProofProposalOptions<[LegacyIndyProofFormatService]> + ): Promise> { this.assertOnlyIndyFormat(proofFormats) const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) @@ -243,8 +230,13 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< public async acceptProposal( agentContext: AgentContext, - { proofRecord, proofFormats, comment, autoAcceptProof }: AcceptProofProposalOptions<[IndyProofFormatServiceLike]> - ): Promise> { + { + proofRecord, + proofFormats, + comment, + autoAcceptProof, + }: ProofProtocolOptions.AcceptProofProposalOptions<[LegacyIndyProofFormatService]> + ): Promise> { // Assert proofRecord.assertProtocolVersion('v1') proofRecord.assertState(ProofState.ProposalReceived) @@ -307,8 +299,13 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< public async negotiateProposal( agentContext: AgentContext, - { proofFormats, proofRecord, comment, autoAcceptProof }: NegotiateProofProposalOptions<[IndyProofFormatServiceLike]> - ): Promise> { + { + proofFormats, + proofRecord, + comment, + autoAcceptProof, + }: ProofProtocolOptions.NegotiateProofProposalOptions<[LegacyIndyProofFormatService]> + ): Promise> { // Assert proofRecord.assertProtocolVersion('v1') proofRecord.assertState(ProofState.ProposalReceived) @@ -351,8 +348,8 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< comment, parentThreadId, autoAcceptProof, - }: CreateProofRequestOptions<[IndyProofFormatServiceLike]> - ): Promise> { + }: ProofProtocolOptions.CreateProofRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { this.assertOnlyIndyFormat(proofFormats) const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) @@ -365,7 +362,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< // Create record const proofRecord = new ProofExchangeRecord({ connectionId: connectionRecord?.id, - threadId: uuid(), + threadId: utils.uuid(), parentThreadId, state: ProofState.RequestSent, autoAcceptProof, @@ -490,8 +487,13 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< public async negotiateRequest( agentContext: AgentContext, - { proofFormats, proofRecord, comment, autoAcceptProof }: NegotiateProofRequestOptions<[IndyProofFormatServiceLike]> - ): Promise> { + { + proofFormats, + proofRecord, + comment, + autoAcceptProof, + }: ProofProtocolOptions.NegotiateProofRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { // Assert proofRecord.assertProtocolVersion('v1') proofRecord.assertState(ProofState.RequestReceived) @@ -538,8 +540,13 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< public async acceptRequest( agentContext: AgentContext, - { proofRecord, proofFormats, autoAcceptProof, comment }: AcceptProofRequestOptions<[IndyProofFormatServiceLike]> - ): Promise> { + { + proofRecord, + proofFormats, + autoAcceptProof, + comment, + }: ProofProtocolOptions.AcceptProofRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { // Assert proofRecord.assertProtocolVersion('v1') proofRecord.assertState(ProofState.RequestReceived) @@ -610,8 +617,8 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< public async getCredentialsForRequest( agentContext: AgentContext, - { proofRecord, proofFormats }: GetCredentialsForRequestOptions<[IndyProofFormatServiceLike]> - ): Promise> { + { proofRecord, proofFormats }: ProofProtocolOptions.GetCredentialsForRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { if (proofFormats) this.assertOnlyIndyFormat(proofFormats) const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -667,8 +674,11 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< public async selectCredentialsForRequest( agentContext: AgentContext, - { proofRecord, proofFormats }: SelectCredentialsForRequestOptions<[IndyProofFormatServiceLike]> - ): Promise> { + { + proofRecord, + proofFormats, + }: ProofProtocolOptions.SelectCredentialsForRequestOptions<[LegacyIndyProofFormatService]> + ): Promise> { if (proofFormats) this.assertOnlyIndyFormat(proofFormats) const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -790,8 +800,8 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< public async acceptPresentation( agentContext: AgentContext, - { proofRecord }: AcceptPresentationOptions - ): Promise> { + { proofRecord }: ProofProtocolOptions.AcceptPresentationOptions + ): Promise> { agentContext.config.logger.debug(`Creating presentation ack for proof record with id ${proofRecord.id}`) // Assert @@ -855,8 +865,8 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< public async createProblemReport( agentContext: AgentContext, - { proofRecord, description }: CreateProofProblemReportOptions - ): Promise> { + { proofRecord, description }: ProofProtocolOptions.CreateProofProblemReportOptions + ): Promise> { const message = new V1PresentationProblemReportMessage({ description: { code: PresentationProblemReportReason.Abandoned, @@ -886,7 +896,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) - const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + const autoAccept = composeProofAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) // Handle always / never cases if (autoAccept === AutoAcceptProof.Always) return true @@ -929,7 +939,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) - const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + const autoAccept = composeProofAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) // Handle always / never cases if (autoAccept === AutoAcceptProof.Always) return true @@ -948,7 +958,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< version: '1.0', attributes: proposalMessage.presentationProposal.attributes, predicates: proposalMessage.presentationProposal.predicates, - }).toJSON() + }) return this.indyProofFormat.shouldAutoRespondToRequest(agentContext, { proofRecord, @@ -972,7 +982,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< const proofsModuleConfig = agentContext.dependencyManager.resolve(ProofsModuleConfig) - const autoAccept = composeAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) + const autoAccept = composeProofAutoAccept(proofRecord.autoAcceptProof, proofsModuleConfig.autoAcceptProofs) // Handle always / never cases if (autoAccept === AutoAcceptProof.Always) return true @@ -1082,7 +1092,7 @@ export class V1ProofProtocol extends BaseProofProtocol implements ProofProtocol< return { proposal: proposalMessage ? { - indy: indyProposeProof?.toJSON(), + indy: indyProposeProof, } : undefined, request: requestMessage diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/V1ProofProtocol.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/V1ProofProtocol.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-negotiation.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.e2e.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-presentation.test.e2e.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.e2e.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.e2e.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proof-request.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proof-request.e2e.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/v1-indy-proofs.e2e.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts b/packages/anoncreds/src/protocols/proofs/v1/errors/V1PresentationProblemReportError.ts similarity index 62% rename from packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts rename to packages/anoncreds/src/protocols/proofs/v1/errors/V1PresentationProblemReportError.ts index 27c77c0f82..4ec7280eae 100644 --- a/packages/core/src/modules/proofs/protocol/v1/errors/V1PresentationProblemReportError.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/errors/V1PresentationProblemReportError.ts @@ -1,8 +1,8 @@ -import type { ProblemReportErrorOptions } from '../../../../problem-reports' -import type { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' +import type { ProblemReportErrorOptions, PresentationProblemReportReason } from '@aries-framework/core' -import { ProblemReportError } from '../../../../problem-reports' -import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' +import { ProblemReportError } from '@aries-framework/core' + +import { V1PresentationProblemReportMessage } from '../messages' interface V1PresentationProblemReportErrorOptions extends ProblemReportErrorOptions { problemCode: PresentationProblemReportReason diff --git a/packages/core/src/modules/proofs/protocol/v1/errors/index.ts b/packages/anoncreds/src/protocols/proofs/v1/errors/index.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/errors/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/errors/index.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationAckHandler.ts similarity index 93% rename from packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationAckHandler.ts index c8f331c3a8..dc087fe1af 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationAckHandler.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationAckHandler.ts @@ -1,5 +1,5 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1PresentationAckMessage } from '../messages' diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationHandler.ts similarity index 90% rename from packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationHandler.ts index e5553b1283..20732e3ecf 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationHandler.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationHandler.ts @@ -1,9 +1,8 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { ProofExchangeRecord } from '../../../repository' import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, ProofExchangeRecord } from '@aries-framework/core' + +import { OutboundMessageContext, DidCommMessageRepository } from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' -import { DidCommMessageRepository } from '../../../../../storage' import { V1PresentationMessage, V1RequestPresentationMessage } from '../messages' export class V1PresentationHandler implements MessageHandler { diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationProblemReportHandler.ts similarity index 94% rename from packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationProblemReportHandler.ts index eee0266a68..4106bbf3f9 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1PresentationProblemReportHandler.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1PresentationProblemReportHandler.ts @@ -1,5 +1,5 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1ProposePresentationHandler.ts similarity index 86% rename from packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/V1ProposePresentationHandler.ts index 113c695c98..2193f6e733 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1ProposePresentationHandler.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1ProposePresentationHandler.ts @@ -1,8 +1,8 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, ProofExchangeRecord } from '@aries-framework/core' + +import { OutboundMessageContext } from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' import { V1ProposePresentationMessage } from '../messages' export class V1ProposePresentationHandler implements MessageHandler { diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1RequestPresentationHandler.ts similarity index 86% rename from packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/V1RequestPresentationHandler.ts index 340307e50a..f0309ddee3 100644 --- a/packages/core/src/modules/proofs/protocol/v1/handlers/V1RequestPresentationHandler.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/handlers/V1RequestPresentationHandler.ts @@ -1,11 +1,14 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' import type { V1ProofProtocol } from '../V1ProofProtocol' +import type { MessageHandler, MessageHandlerInboundMessage, ProofExchangeRecord } from '@aries-framework/core' + +import { + OutboundMessageContext, + RoutingService, + ServiceDecorator, + DidCommMessageRepository, + DidCommMessageRole, +} from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' -import { ServiceDecorator } from '../../../../../decorators/service/ServiceDecorator' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' -import { RoutingService } from '../../../../routing' import { V1RequestPresentationMessage } from '../messages' export class V1RequestPresentationHandler implements MessageHandler { diff --git a/packages/core/src/modules/proofs/protocol/v1/handlers/index.ts b/packages/anoncreds/src/protocols/proofs/v1/handlers/index.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/handlers/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/handlers/index.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/index.ts b/packages/anoncreds/src/protocols/proofs/v1/index.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/index.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationAckMessage.ts similarity index 64% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationAckMessage.ts index 29f12a8dd9..743fdba4fa 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationAckMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationAckMessage.ts @@ -1,7 +1,6 @@ -import type { AckMessageOptions } from '../../../../common' +import type { AckMessageOptions } from '@aries-framework/core' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { AckMessage } from '../../../../common' +import { AckMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export class V1PresentationAckMessage extends AckMessage { public constructor(options: AckMessageOptions) { diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationMessage.ts similarity index 84% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationMessage.ts index 12d2978ab0..e27309111c 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationMessage.ts @@ -1,12 +1,9 @@ -import type { IndyProof } from 'indy-sdk' +import type { AnonCredsProof } from '../../../../models' +import { Attachment, AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class-validator' -import { AgentMessage } from '../../../../../agent/AgentMessage' -import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' - export const INDY_PROOF_ATTACHMENT_ID = 'libindy-presentation-0' export interface V1PresentationMessageOptions { @@ -57,11 +54,11 @@ export class V1PresentationMessage extends AgentMessage { @IsInstance(Attachment, { each: true }) public presentationAttachments!: Attachment[] - public get indyProof(): IndyProof | null { + public get indyProof(): AnonCredsProof | null { const attachment = this.presentationAttachments.find((attachment) => attachment.id === INDY_PROOF_ATTACHMENT_ID) ?? null - const proofJson = attachment?.getDataAsJson() ?? null + const proofJson = attachment?.getDataAsJson() ?? null return proofJson } diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationProblemReportMessage.ts similarity index 72% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationProblemReportMessage.ts index 87901ce6a8..baa7d1935e 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1PresentationProblemReportMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1PresentationProblemReportMessage.ts @@ -1,7 +1,6 @@ -import type { ProblemReportMessageOptions } from '../../../../problem-reports/messages/ProblemReportMessage' +import type { ProblemReportMessageOptions } from '@aries-framework/core' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' -import { ProblemReportMessage } from '../../../../problem-reports/messages/ProblemReportMessage' +import { ProblemReportMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export type V1PresentationProblemReportMessageOptions = ProblemReportMessageOptions diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1ProposePresentationMessage.ts similarity index 91% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1ProposePresentationMessage.ts index bef0a44c7a..12d94f73fa 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1ProposePresentationMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1ProposePresentationMessage.ts @@ -1,8 +1,7 @@ +import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' -import { AgentMessage } from '../../../../../agent/AgentMessage' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' import { V1PresentationPreview } from '../models/V1PresentationPreview' export interface V1ProposePresentationMessageOptions { diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/V1RequestPresentationMessage.ts similarity index 82% rename from packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/V1RequestPresentationMessage.ts index 5ac5fd6798..e49dfd9aaa 100644 --- a/packages/core/src/modules/proofs/protocol/v1/messages/V1RequestPresentationMessage.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/messages/V1RequestPresentationMessage.ts @@ -1,12 +1,9 @@ -import type { IndyProofRequest } from 'indy-sdk' +import type { LegacyIndyProofRequest } from '../../../../formats' +import { Attachment, AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsArray, IsString, ValidateNested, IsOptional, IsInstance } from 'class-validator' -import { AgentMessage } from '../../../../../agent/AgentMessage' -import { Attachment } from '../../../../../decorators/attachment/Attachment' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' - export interface V1RequestPresentationMessageOptions { id?: string comment?: string @@ -54,10 +51,10 @@ export class V1RequestPresentationMessage extends AgentMessage { @IsInstance(Attachment, { each: true }) public requestAttachments!: Attachment[] - public get indyProofRequest(): IndyProofRequest | null { + public get indyProofRequest(): LegacyIndyProofRequest | null { const attachment = this.requestAttachments.find((attachment) => attachment.id === INDY_PROOF_REQUEST_ATTACHMENT_ID) // Extract proof request from attachment - return attachment?.getDataAsJson() ?? null + return attachment?.getDataAsJson() ?? null } public getRequestAttachmentById(id: string): Attachment | undefined { diff --git a/packages/core/src/modules/proofs/protocol/v1/messages/index.ts b/packages/anoncreds/src/protocols/proofs/v1/messages/index.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/messages/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/messages/index.ts diff --git a/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts b/packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts similarity index 87% rename from packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts rename to packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts index 0de67b3a00..7e651dea57 100644 --- a/packages/core/src/modules/proofs/protocol/v1/models/V1PresentationPreview.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts @@ -1,6 +1,7 @@ +import { JsonTransformer, IsValidMessageType, replaceLegacyDidSovPrefix, parseMessageType } from '@aries-framework/core' import { Expose, Transform, Type } from 'class-transformer' import { - IsEnum, + IsIn, IsInstance, IsInt, IsMimeType, @@ -11,10 +12,8 @@ import { ValidateNested, } from 'class-validator' -import { credDefIdRegex } from '../../../../../utils' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { IsValidMessageType, parseMessageType, replaceLegacyDidSovPrefix } from '../../../../../utils/messageType' -import { PredicateType } from '../../../formats/indy/models/PredicateType' +import { anonCredsPredicateType, AnonCredsPredicateType } from '../../../../models' +import { legacyIndyCredentialDefinitionIdRegex } from '../../../../utils' export interface V1PresentationPreviewAttributeOptions { name: string @@ -40,7 +39,7 @@ export class V1PresentationPreviewAttribute { @Expose({ name: 'cred_def_id' }) @IsString() @ValidateIf((o: V1PresentationPreviewAttribute) => o.referent !== undefined) - @Matches(credDefIdRegex) + @Matches(legacyIndyCredentialDefinitionIdRegex) public credentialDefinitionId?: string @Expose({ name: 'mime-type' }) @@ -64,7 +63,7 @@ export class V1PresentationPreviewAttribute { export interface V1PresentationPreviewPredicateOptions { name: string credentialDefinitionId: string - predicate: PredicateType + predicate: AnonCredsPredicateType threshold: number } @@ -83,11 +82,11 @@ export class V1PresentationPreviewPredicate { @Expose({ name: 'cred_def_id' }) @IsString() - @Matches(credDefIdRegex) + @Matches(legacyIndyCredentialDefinitionIdRegex) public credentialDefinitionId!: string - @IsEnum(PredicateType) - public predicate!: PredicateType + @IsIn(anonCredsPredicateType) + public predicate!: AnonCredsPredicateType @IsInt() public threshold!: number diff --git a/packages/core/src/modules/proofs/protocol/v1/models/index.ts b/packages/anoncreds/src/protocols/proofs/v1/models/index.ts similarity index 100% rename from packages/core/src/modules/proofs/protocol/v1/models/index.ts rename to packages/anoncreds/src/protocols/proofs/v1/models/index.ts diff --git a/packages/anoncreds/src/utils/__tests__/legacyIndyIdentifiers.test.ts b/packages/anoncreds/src/utils/__tests__/legacyIndyIdentifiers.test.ts new file mode 100644 index 0000000000..2d38390ae9 --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/legacyIndyIdentifiers.test.ts @@ -0,0 +1,34 @@ +import { + legacyIndyCredentialDefinitionIdRegex, + legacyIndyDidRegex, + legacyIndySchemaIdRegex, + legacyIndySchemaVersionRegex, +} from '../legacyIndyIdentifiers' + +describe('Legacy Indy Identifier Regex', () => { + const invalidTest = 'test' + + test('test for legacyIndyCredentialDefinitionIdRegex', async () => { + const test = 'q7ATwTYbQDgiigVijUAej:3:CL:160971:1.0.0' + expect(test).toMatch(legacyIndyCredentialDefinitionIdRegex) + expect(legacyIndyCredentialDefinitionIdRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndyDidRegex', async () => { + const test = 'did:sov:q7ATwTYbQDgiigVijUAej' + expect(test).toMatch(legacyIndyDidRegex) + expect(legacyIndyDidRegex.test(invalidTest)).toBeFalsy + }) + + test('test for legacyIndySchemaIdRegex', async () => { + const test = 'q7ATwTYbQDgiigVijUAej:2:test:1.0' + expect(test).toMatch(legacyIndySchemaIdRegex) + expect(legacyIndySchemaIdRegex.test(invalidTest)).toBeFalsy + }) + + test('test for legacyIndySchemaVersionRegex', async () => { + const test = '1.0.0' + expect(test).toMatch(legacyIndySchemaVersionRegex) + expect(legacyIndySchemaVersionRegex.test(invalidTest)).toBeFalsy + }) +}) diff --git a/packages/anoncreds/src/utils/index.ts b/packages/anoncreds/src/utils/index.ts index 6c9cbde0c1..5e1ff3f413 100644 --- a/packages/anoncreds/src/utils/index.ts +++ b/packages/anoncreds/src/utils/index.ts @@ -8,3 +8,9 @@ export { encodeCredentialValue, checkValidCredentialValueEncoding } from './cred export { IsMap } from './isMap' export { composeCredentialAutoAccept, composeProofAutoAccept } from './composeAutoAccept' export { areCredentialPreviewAttributesEqual } from './credentialPreviewAttributes' +export { + legacyIndyCredentialDefinitionIdRegex, + legacyIndyDidRegex, + legacyIndySchemaIdRegex, + legacyIndySchemaVersionRegex, +} from './legacyIndyIdentifiers' diff --git a/packages/anoncreds/src/utils/legacyIndyIdentifiers.ts b/packages/anoncreds/src/utils/legacyIndyIdentifiers.ts new file mode 100644 index 0000000000..29cc3f45d6 --- /dev/null +++ b/packages/anoncreds/src/utils/legacyIndyIdentifiers.ts @@ -0,0 +1,5 @@ +export const legacyIndySchemaIdRegex = /^[a-zA-Z0-9]{21,22}:2:.+:[0-9.]+$/ +export const legacyIndySchemaVersionRegex = /^(\d+\.)?(\d+\.)?(\*|\d+)$/ +export const legacyIndyCredentialDefinitionIdRegex = + /^([a-zA-Z0-9]{21,22}):3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+)):(.+)?$/ +export const legacyIndyDidRegex = /^(did:sov:)?[a-zA-Z0-9]{21,22}$/ diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 0778a37bea..b601dfce68 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -1,24 +1,42 @@ +import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { + AnonCredsRegisterCredentialDefinitionOptions, + AnonCredsRequestedAttribute, + AnonCredsRequestedPredicate, + AnonCredsOfferCredentialFormat, + AnonCredsSchema, + RegisterCredentialDefinitionReturnStateFinished, + RegisterSchemaReturnStateFinished, +} from '../src' +import type { + AutoAcceptProof, + BaseEvent, + ConnectionRecord, + CredentialStateChangedEvent, + ProofStateChangedEvent, +} from '@aries-framework/core' + import { Agent, AriesFrameworkError, AutoAcceptCredential, - AutoAcceptProof, - BaseEvent, CredentialEventTypes, CredentialsModule, CredentialState, - CredentialStateChangedEvent, ProofEventTypes, ProofsModule, ProofState, - ProofStateChangedEvent, - V1CredentialProtocol, - V1ProofProtocol, V2CredentialProtocol, V2ProofProtocol, + DidsModule, } from '@aries-framework/core' +import testLogger from '@aries-framework/core/tests/logger' +import { randomUUID } from 'crypto' +import indySdk from 'indy-sdk' import { ReplaySubject, Subject } from 'rxjs' -import { SubjectInboundTransport, SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' + +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { genesisPath, getAgentOptions, @@ -27,20 +45,7 @@ import { taaVersion, waitForCredentialRecordSubject, waitForProofExchangeRecordSubject, -} from '@aries-framework/core/tests/helpers' -import { - AnonCredsModule, - AnonCredsOfferCredentialFormat, - AnonCredsSchema, - RegisterCredentialDefinitionReturnStateFinished, - RegisterSchemaReturnStateFinished, -} from '../src' - -import { DidsModule } from '@aries-framework/core' -import { randomUUID } from 'crypto' -import indySdk from 'indy-sdk' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import testLogger from '@aries-framework/core/tests/logger' +} from '../../core/tests/helpers' import { IndySdkAnonCredsRegistry, IndySdkModule, @@ -48,13 +53,13 @@ import { IndySdkSovDidResolver, } from '../../indy-sdk/src' import { - AnonCredsRegisterCredentialDefinitionOptions, + V1CredentialProtocol, + V1ProofProtocol, + AnonCredsModule, LegacyIndyCredentialFormatService, LegacyIndyProofFormatService, } from '../src' -import { AnonCredsRequestedAttribute, AnonCredsRequestedPredicate } from '../src' - // Helper type to get the type of the agents (with the custom modules) for the credential tests export type AnonCredsTestsAgent = Agent> @@ -241,19 +246,42 @@ export async function issueLegacyAnonCredsCredential({ } } -export async function setupAnonCredsTests({ +interface SetupAnonCredsTestReturn { + issuerAgent: AnonCredsTestsAgent + issuerReplay: ReplaySubject + + holderAgent: AnonCredsTestsAgent + holderReplay: ReplaySubject + + issuerHolderConnectionId: string + holderIssuerConnectionId: string + + verifierHolderConnectionId: VerifierName extends string ? string : undefined + holderVerifierConnectionId: VerifierName extends string ? string : undefined + + verifierAgent: VerifierName extends string ? AnonCredsTestsAgent : undefined + verifierReplay: VerifierName extends string + ? ReplaySubject + : undefined + + credentialDefinitionId: string +} + +export async function setupAnonCredsTests({ issuerName, holderName, verifierName, autoAcceptCredentials, autoAcceptProofs, + attributeNames, }: { issuerName: string holderName: string - verifierName: string + verifierName?: VerifierName autoAcceptCredentials?: AutoAcceptCredential autoAcceptProofs?: AutoAcceptProof -}) { + attributeNames: string[] +}): Promise> { const issuerMessages = new Subject() const holderMessages = new Subject() const verifierMessages = new Subject() @@ -294,46 +322,60 @@ export async function setupAnonCredsTests({ holderAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await holderAgent.initialize() - const verifierAgent = new Agent( - getAgentOptions( - verifierName, - { - endpoints: ['rxjs:verifier'], - }, - modules + let verifierAgent: AnonCredsTestsAgent | undefined + if (verifierName) { + verifierAgent = new Agent( + getAgentOptions( + verifierName, + { + endpoints: ['rxjs:verifier'], + }, + modules + ) ) - ) - verifierAgent.registerInboundTransport(new SubjectInboundTransport(verifierMessages)) - verifierAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await verifierAgent.initialize() + verifierAgent.registerInboundTransport(new SubjectInboundTransport(verifierMessages)) + verifierAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await verifierAgent.initialize() + } const { credentialDefinition, schema } = await prepareForAnonCredsIssuance(issuerAgent, { - attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], + attributeNames, // TODO: replace with more dynamic / generic value We should create a did using the dids module // and use that probably issuerId: issuerAgent.publicDid?.did as string, }) const [issuerHolderConnection, holderIssuerConnection] = await makeConnection(issuerAgent, holderAgent) - const [holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) + + let verifierHolderConnection: ConnectionRecord | undefined + let holderVerifierConnection: ConnectionRecord | undefined + if (verifierAgent) { + ;[holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) + } const issuerReplay = new ReplaySubject() const holderReplay = new ReplaySubject() - const verifierReplay = new ReplaySubject() + + const verifierReplay = verifierAgent + ? new ReplaySubject() + : undefined issuerAgent.events .observable(CredentialEventTypes.CredentialStateChanged) .subscribe(issuerReplay) + issuerAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(issuerReplay) + holderAgent.events .observable(CredentialEventTypes.CredentialStateChanged) .subscribe(holderReplay) - verifierAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(verifierReplay) - - issuerAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(issuerReplay) holderAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(holderReplay) - verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) + + if (verifierAgent) { + verifierAgent.events + .observable(CredentialEventTypes.CredentialStateChanged) + .subscribe(verifierReplay) + verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) + } return { issuerAgent, @@ -350,9 +392,9 @@ export async function setupAnonCredsTests({ issuerHolderConnectionId: issuerHolderConnection.id, holderIssuerConnectionId: holderIssuerConnection.id, - holderVerifierConnectionId: holderVerifierConnection.id, - verifierHolderConnectionId: verifierHolderConnection.id, - } + holderVerifierConnectionId: holderVerifierConnection?.id, + verifierHolderConnectionId: verifierHolderConnection?.id, + } as unknown as SetupAnonCredsTestReturn } export async function prepareForAnonCredsIssuance( diff --git a/packages/core/package.json b/packages/core/package.json index c990e6f480..7b3dd8da9b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -33,7 +33,6 @@ "@types/node-fetch": "^2.5.10", "@types/ws": "^7.4.6", "abort-controller": "^3.0.0", - "bn.js": "^5.2.0", "borc": "^3.0.0", "buffer": "^6.0.3", "class-transformer": "0.5.1", diff --git a/packages/core/src/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 84e282bf80..d6fc38bb83 100644 --- a/packages/core/src/agent/Agent.ts +++ b/packages/core/src/agent/Agent.ts @@ -1,6 +1,7 @@ import type { AgentDependencies } from './AgentDependencies' import type { AgentModulesInput } from './AgentModules' import type { AgentMessageReceivedEvent } from './Events' +import type { Module } from '../plugins' import type { InboundTransport } from '../transport/InboundTransport' import type { OutboundTransport } from '../transport/OutboundTransport' import type { InitConfig } from '../types' @@ -161,6 +162,16 @@ export class Agent extends BaseAge public async initialize() { await super.initialize() + for (const [moduleKey, module] of Object.entries(this.dependencyManager.registeredModules) as [string, Module][]) { + this.logger.debug(`initializing module ${moduleKey}`, { + hasInitializationMethod: module.initialize !== undefined, + }) + + if (module.initialize) { + await module.initialize(this.agentContext) + } + } + for (const transport of this.inboundTransports) { await transport.start(this) } diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index 6b5d5fb258..05643c9332 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,10 +1,12 @@ import type { AgentContext } from '../../agent' import type { Key, Wallet } from '@aries-framework/core' +import indySdk from 'indy-sdk' + +import { IndySdkWallet } from '../../../../indy-sdk/src' import { getAgentConfig, getAgentContext } from '../../../tests/helpers' import { DidKey } from '../../modules/dids' import { Buffer, JsonEncoder } from '../../utils' -import { IndyWallet } from '../../wallet/IndyWallet' import { JwsService } from '../JwsService' import { KeyType } from '../KeyType' import { SigningProviderRegistry } from '../signing-provider' @@ -20,12 +22,12 @@ describe('JwsService', () => { let didJwsz6MkvKey: Key beforeAll(async () => { const config = getAgentConfig('JwsService') - wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) + // TODO: update to InMemoryWallet + wallet = new IndySdkWallet(indySdk, config.logger, new SigningProviderRegistry([])) agentContext = getAgentContext({ wallet, }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) + await wallet.createAndOpen(config.walletConfig) jwsService = new JwsService() didJwsz6MkfKey = await wallet.createKey({ seed: didJwsz6Mkf.SEED, keyType: KeyType.Ed25519 }) diff --git a/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts b/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts deleted file mode 100644 index 41a5ed808b..0000000000 --- a/packages/core/src/modules/credentials/errors/CredentialProblemReportError.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { CredentialProblemReportReason } from './CredentialProblemReportReason' -import type { ProblemReportErrorOptions } from '../../problem-reports' - -import { V1CredentialProblemReportMessage } from '../protocol/v1/messages' - -import { ProblemReportError } from './../../problem-reports/errors/ProblemReportError' - -interface CredentialProblemReportErrorOptions extends ProblemReportErrorOptions { - problemCode: CredentialProblemReportReason -} -export class CredentialProblemReportError extends ProblemReportError { - public problemReport: V1CredentialProblemReportMessage - - public constructor(message: string, { problemCode }: CredentialProblemReportErrorOptions) { - super(message, { problemCode }) - this.problemReport = new V1CredentialProblemReportMessage({ - description: { - en: message, - code: problemCode, - }, - }) - } -} diff --git a/packages/core/src/modules/credentials/errors/index.ts b/packages/core/src/modules/credentials/errors/index.ts deleted file mode 100644 index 3d5c266524..0000000000 --- a/packages/core/src/modules/credentials/errors/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './CredentialProblemReportError' -export * from './CredentialProblemReportReason' diff --git a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts b/packages/core/src/modules/credentials/formats/jsonld/__tests__/JsonLdCredentialFormatService.test.ts similarity index 90% rename from packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts rename to packages/core/src/modules/credentials/formats/jsonld/__tests__/JsonLdCredentialFormatService.test.ts index 59761f5808..dfa001c528 100644 --- a/packages/core/src/modules/credentials/formats/__tests__/JsonLdCredentialFormatService.test.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/__tests__/JsonLdCredentialFormatService.test.ts @@ -1,33 +1,26 @@ -import type { AgentContext } from '../../../../agent' -import type { CredentialFormatService } from '../../formats' -import type { - JsonCredential, - JsonLdCredentialFormat, - JsonLdCredentialDetailFormat, -} from '../../formats/jsonld/JsonLdCredentialFormat' -import type { CredentialPreviewAttribute } from '../../models/CredentialPreviewAttribute' -import type { V2OfferCredentialMessageOptions } from '../../protocol/v2/messages/V2OfferCredentialMessage' -import type { CustomCredentialTags } from '../../repository/CredentialExchangeRecord' - -import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' -import { Attachment, AttachmentData } from '../../../../decorators/attachment/Attachment' -import { JsonTransformer } from '../../../../utils' -import { JsonEncoder } from '../../../../utils/JsonEncoder' -import { DidDocument } from '../../../dids' -import { DidResolverService } from '../../../dids/services/DidResolverService' -import { W3cCredentialRecord, W3cCredentialService } from '../../../vc' -import { Ed25519Signature2018Fixtures } from '../../../vc/__tests__/fixtures' -import { CREDENTIALS_CONTEXT_V1_URL } from '../../../vc/constants' -import { W3cVerifiableCredential } from '../../../vc/models' -import { JsonLdCredentialFormatService } from '../../formats/jsonld/JsonLdCredentialFormatService' -import { CredentialState } from '../../models' -import { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID } from '../../protocol/v1/messages' -import { V2CredentialPreview } from '../../protocol/v2/messages' -import { V2OfferCredentialMessage } from '../../protocol/v2/messages/V2OfferCredentialMessage' -import { CredentialExchangeRecord } from '../../repository/CredentialExchangeRecord' - -jest.mock('../../../vc/W3cCredentialService') -jest.mock('../../../dids/services/DidResolverService') +import type { CredentialFormatService } from '../..' +import type { AgentContext } from '../../../../../agent' +import type { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAttribute' +import type { CustomCredentialTags } from '../../../repository/CredentialExchangeRecord' +import type { JsonCredential, JsonLdCredentialFormat, JsonLdCredentialDetailFormat } from '../JsonLdCredentialFormat' + +import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../../tests/helpers' +import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' +import { JsonTransformer } from '../../../../../utils' +import { JsonEncoder } from '../../../../../utils/JsonEncoder' +import { DidDocument } from '../../../../dids' +import { DidResolverService } from '../../../../dids/services/DidResolverService' +import { W3cCredentialRecord, W3cCredentialService } from '../../../../vc' +import { Ed25519Signature2018Fixtures } from '../../../../vc/__tests__/fixtures' +import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' +import { W3cVerifiableCredential } from '../../../../vc/models' +import { CredentialState } from '../../../models' +import { V2CredentialPreview } from '../../../protocol/v2/messages' +import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { JsonLdCredentialFormatService } from '../JsonLdCredentialFormatService' + +jest.mock('../../../../vc/W3cCredentialService') +jest.mock('../../../../dids/services/DidResolverService') const W3cCredentialServiceMock = W3cCredentialService as jest.Mock const DidResolverServiceMock = DidResolverService as jest.Mock @@ -113,26 +106,11 @@ const mockCredentialRecord = ({ id?: string credentialAttributes?: CredentialPreviewAttribute[] } = {}) => { - const offerOptions: V2OfferCredentialMessageOptions = { - id: '', - formats: [ - { - attachmentId: INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, - format: 'hlindy/cred-abstract@v2.0', - }, - ], - comment: 'some comment', - credentialPreview: credentialPreview, - offerAttachments: [offerAttachment], - replacementId: undefined, - } - const offerMessage = new V2OfferCredentialMessage(offerOptions) - const credentialRecord = new CredentialExchangeRecord({ id, credentialAttributes: credentialAttributes || credentialPreview.attributes, state: state || CredentialState.OfferSent, - threadId: threadId ?? offerMessage.id, + threadId: threadId ?? 'add7e1a0-109e-4f37-9caa-cfd0fcdfe540', connectionId: connectionId ?? '123', tags, protocolVersion: 'v2', diff --git a/packages/core/src/modules/credentials/index.ts b/packages/core/src/modules/credentials/index.ts index 286f34276d..d34680afe1 100644 --- a/packages/core/src/modules/credentials/index.ts +++ b/packages/core/src/modules/credentials/index.ts @@ -7,4 +7,3 @@ export * from './formats' export * from './protocol' export * from './CredentialsModule' export * from './CredentialsModuleConfig' -export { CredentialProblemReportError, CredentialProblemReportReason } from './errors' diff --git a/packages/core/src/modules/credentials/errors/CredentialProblemReportReason.ts b/packages/core/src/modules/credentials/models/CredentialProblemReportReason.ts similarity index 100% rename from packages/core/src/modules/credentials/errors/CredentialProblemReportReason.ts rename to packages/core/src/modules/credentials/models/CredentialProblemReportReason.ts diff --git a/packages/core/src/modules/credentials/models/index.ts b/packages/core/src/modules/credentials/models/index.ts index 0db2bca14c..bec3b0ce2f 100644 --- a/packages/core/src/modules/credentials/models/index.ts +++ b/packages/core/src/modules/credentials/models/index.ts @@ -3,3 +3,4 @@ export * from './CredentialPreviewAttribute' export * from './CredentialAutoAcceptType' export * from './CredentialFormatSpec' export * from './CredentialState' +export * from './CredentialProblemReportReason' diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts index df39187210..ee6fdd9ace 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts @@ -36,8 +36,7 @@ import { uuid } from '../../../../utils/uuid' import { AckStatus } from '../../../common' import { ConnectionService } from '../../../connections' import { CredentialsModuleConfig } from '../../CredentialsModuleConfig' -import { CredentialProblemReportReason } from '../../errors' -import { AutoAcceptCredential, CredentialState } from '../../models' +import { AutoAcceptCredential, CredentialProblemReportReason, CredentialState } from '../../models' import { CredentialExchangeRecord, CredentialRepository } from '../../repository' import { composeAutoAccept } from '../../util/composeAutoAccept' import { arePreviewAttributesEqual } from '../../util/previewAttributes' diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts index 8cddb48b7d..96b91c5a61 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts @@ -20,7 +20,7 @@ import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' import { CredentialEventTypes } from '../../../CredentialEvents' import { credReq } from '../../../__tests__/fixtures' -import { CredentialProblemReportReason } from '../../../errors/CredentialProblemReportReason' +import { CredentialProblemReportReason } from '../../../models/CredentialProblemReportReason' import { IndyCredentialFormatService } from '../../../formats' import { IndyCredentialUtils } from '../../../formats/indy/IndyCredentialUtils' import { JsonLdCredentialFormatService } from '../../../formats/jsonld/JsonLdCredentialFormatService' diff --git a/packages/core/src/modules/credentials/protocol/v2/errors/V2CredentialProblemReportError.ts b/packages/core/src/modules/credentials/protocol/v2/errors/V2CredentialProblemReportError.ts new file mode 100644 index 0000000000..0db9672621 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/errors/V2CredentialProblemReportError.ts @@ -0,0 +1,23 @@ +import type { ProblemReportErrorOptions } from '../../../../problem-reports' +import type { CredentialProblemReportReason } from '../../../models/CredentialProblemReportReason' + +import { ProblemReportError } from '../../../../problem-reports/errors/ProblemReportError' +import { V2CredentialProblemReportMessage } from '../messages/V2CredentialProblemReportMessage' + +export interface V2CredentialProblemReportErrorOptions extends ProblemReportErrorOptions { + problemCode: CredentialProblemReportReason +} + +export class V2CredentialProblemReportError extends ProblemReportError { + public problemReport: V2CredentialProblemReportMessage + + public constructor(message: string, { problemCode }: V2CredentialProblemReportErrorOptions) { + super(message, { problemCode }) + this.problemReport = new V2CredentialProblemReportMessage({ + description: { + en: message, + code: problemCode, + }, + }) + } +} diff --git a/packages/core/src/modules/credentials/protocol/v2/errors/index.ts b/packages/core/src/modules/credentials/protocol/v2/errors/index.ts new file mode 100644 index 0000000000..846017e442 --- /dev/null +++ b/packages/core/src/modules/credentials/protocol/v2/errors/index.ts @@ -0,0 +1 @@ +export { V2CredentialProblemReportError, V2CredentialProblemReportErrorOptions } from './V2CredentialProblemReportError' diff --git a/packages/core/src/modules/credentials/protocol/v2/index.ts b/packages/core/src/modules/credentials/protocol/v2/index.ts index c6d6213662..f50d673645 100644 --- a/packages/core/src/modules/credentials/protocol/v2/index.ts +++ b/packages/core/src/modules/credentials/protocol/v2/index.ts @@ -1,2 +1,3 @@ export * from './V2CredentialProtocol' export * from './messages' +export * from './errors' diff --git a/packages/core/src/modules/dids/DidsModuleConfig.ts b/packages/core/src/modules/dids/DidsModuleConfig.ts index 24acbf38bf..057772d8d8 100644 --- a/packages/core/src/modules/dids/DidsModuleConfig.ts +++ b/packages/core/src/modules/dids/DidsModuleConfig.ts @@ -1,14 +1,6 @@ import type { DidRegistrar, DidResolver } from './domain' -import { - KeyDidRegistrar, - IndySdkSovDidRegistrar, - PeerDidRegistrar, - KeyDidResolver, - PeerDidResolver, - IndySdkSovDidResolver, - WebDidResolver, -} from './methods' +import { KeyDidRegistrar, PeerDidRegistrar, KeyDidResolver, PeerDidResolver, WebDidResolver } from './methods' /** * DidsModuleConfigOptions defines the interface for the options of the DidsModuleConfig class. @@ -23,7 +15,7 @@ export interface DidsModuleConfigOptions { * registered, as it is needed for the connections and out of band module to function. Other did methods can be * disabled. * - * @default [KeyDidRegistrar, IndySdkSovDidRegistrar, PeerDidRegistrar] + * @default [KeyDidRegistrar, PeerDidRegistrar] */ registrars?: DidRegistrar[] @@ -35,7 +27,7 @@ export interface DidsModuleConfigOptions { * registered, as it is needed for the connections and out of band module to function. Other did methods can be * disabled. * - * @default [IndySdkSovDidResolver, WebDidResolver, KeyDidResolver, PeerDidResolver] + * @default [WebDidResolver, KeyDidResolver, PeerDidResolver] */ resolvers?: DidResolver[] } @@ -54,11 +46,7 @@ export class DidsModuleConfig { // This prevents creating new instances every time this property is accessed if (this._registrars) return this._registrars - let registrars = this.options.registrars ?? [ - new KeyDidRegistrar(), - new IndySdkSovDidRegistrar(), - new PeerDidRegistrar(), - ] + let registrars = this.options.registrars ?? [new KeyDidRegistrar(), new PeerDidRegistrar()] // Add peer did registrar if it is not included yet if (!registrars.find((registrar) => registrar instanceof PeerDidRegistrar)) { @@ -79,12 +67,7 @@ export class DidsModuleConfig { // This prevents creating new instances every time this property is accessed if (this._resolvers) return this._resolvers - let resolvers = this.options.resolvers ?? [ - new IndySdkSovDidResolver(), - new WebDidResolver(), - new KeyDidResolver(), - new PeerDidResolver(), - ] + let resolvers = this.options.resolvers ?? [new WebDidResolver(), new KeyDidResolver(), new PeerDidResolver()] // Add peer did resolver if it is not included yet if (!resolvers.find((resolver) => resolver instanceof PeerDidResolver)) { diff --git a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts index 70a08b4d55..4d89be6eaf 100644 --- a/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts +++ b/packages/core/src/modules/oob/__tests__/OutOfBandService.test.ts @@ -1,11 +1,7 @@ -import type { AgentContext } from '../../../agent' -import type { Wallet } from '../../../wallet/Wallet' - import { Subject } from 'rxjs' import { agentDependencies, - getAgentConfig, getAgentContext, getMockConnection, getMockOutOfBand, @@ -14,9 +10,7 @@ import { import { EventEmitter } from '../../../agent/EventEmitter' import { InboundMessageContext } from '../../../agent/models/InboundMessageContext' import { KeyType, Key } from '../../../crypto' -import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { AriesFrameworkError } from '../../../error' -import { IndyWallet } from '../../../wallet/IndyWallet' import { DidExchangeState } from '../../connections/models' import { OutOfBandService } from '../OutOfBandService' import { OutOfBandEventTypes } from '../domain/OutOfBandEvents' @@ -31,24 +25,12 @@ const OutOfBandRepositoryMock = OutOfBandRepository as jest.Mock { - const agentConfig = getAgentConfig('OutOfBandServiceTest') - let wallet: Wallet let outOfBandRepository: OutOfBandRepository let outOfBandService: OutOfBandService let eventEmitter: EventEmitter - let agentContext: AgentContext - - beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) - agentContext = getAgentContext() - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) - }) - - afterAll(async () => { - await wallet.delete() - }) beforeEach(async () => { eventEmitter = new EventEmitter(agentDependencies, new Subject()) diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts index c2012ed566..121226d9e4 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts @@ -5,7 +5,6 @@ import { DependencyManager } from '../../../plugins/DependencyManager' import { ProofsApi } from '../ProofsApi' import { ProofsModule } from '../ProofsModule' import { ProofsModuleConfig } from '../ProofsModuleConfig' -import { V1ProofProtocol } from '../protocol/v1/V1ProofProtocol' import { V2ProofProtocol } from '../protocol/v2/V2ProofProtocol' import { ProofRepository } from '../repository' @@ -37,7 +36,7 @@ describe('ProofsModule', () => { test('registers V1ProofProtocol and V2ProofProtocol if no proofProtocols are configured', () => { const proofsModule = new ProofsModule() - expect(proofsModule.config.proofProtocols).toEqual([expect.any(V1ProofProtocol), expect.any(V2ProofProtocol)]) + expect(proofsModule.config.proofProtocols).toEqual([expect.any(V2ProofProtocol)]) }) test('calls register on the provided ProofProtocols', () => { diff --git a/packages/core/src/modules/proofs/__tests__/fixtures.ts b/packages/core/src/modules/proofs/__tests__/fixtures.ts deleted file mode 100644 index 2045f6f0f8..0000000000 --- a/packages/core/src/modules/proofs/__tests__/fixtures.ts +++ /dev/null @@ -1,47 +0,0 @@ -export const credDef = { - ver: '1.0', - id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:16:TAG', - schemaId: '16', - type: 'CL', - tag: 'TAG', - value: { - primary: { - n: '92498022445845202032348897620554299694896009176315493627722439892023558526259875239808280186111059586069456394012963552956574651629517633396592827947162983189649269173220440607665417484696688946624963596710652063849006738050417440697782608643095591808084344059908523401576738321329706597491345875134180790935098782801918369980296355919072827164363500681884641551147645504164254206270541724042784184712124576190438261715948768681331862924634233043594086219221089373455065715714369325926959533971768008691000560918594972006312159600845441063618991760512232714992293187779673708252226326233136573974603552763615191259713', - s: '10526250116244590830801226936689232818708299684432892622156345407187391699799320507237066062806731083222465421809988887959680863378202697458984451550048737847231343182195679453915452156726746705017249911605739136361885518044604626564286545453132948801604882107628140153824106426249153436206037648809856342458324897885659120708767794055147846459394129610878181859361616754832462886951623882371283575513182530118220334228417923423365966593298195040550255217053655606887026300020680355874881473255854564974899509540795154002250551880061649183753819902391970912501350100175974791776321455551753882483918632271326727061054', - r: [Object], - rctxt: - '46370806529776888197599056685386177334629311939451963919411093310852010284763705864375085256873240323432329015015526097014834809926159013231804170844321552080493355339505872140068998254185756917091385820365193200970156007391350745837300010513687490459142965515562285631984769068796922482977754955668569724352923519618227464510753980134744424528043503232724934196990461197793822566137436901258663918660818511283047475389958180983391173176526879694302021471636017119966755980327241734084462963412467297412455580500138233383229217300797768907396564522366006433982511590491966618857814545264741708965590546773466047139517', - z: '84153935869396527029518633753040092509512111365149323230260584738724940130382637900926220255597132853379358675015222072417404334537543844616589463419189203852221375511010886284448841979468767444910003114007224993233448170299654815710399828255375084265247114471334540928216537567325499206413940771681156686116516158907421215752364889506967984343660576422672840921988126699885304325384925457260272972771547695861942114712679509318179363715259460727275178310181122162544785290813713205047589943947592273130618286905125194410421355167030389500160371886870735704712739886223342214864760968555566496288314800410716250791012', - }, - }, -} - -export const TEST_INPUT_DESCRIPTORS_CITIZENSHIP = { - constraints: { - fields: [ - { - path: ['$.credentialSubject.familyName'], - purpose: 'The claim must be from one of the specified issuers', - id: '1f44d55f-f161-4938-a659-f8026467f126', - }, - { - path: ['$.credentialSubject.givenName'], - purpose: 'The claim must be from one of the specified issuers', - }, - ], - }, - schema: [ - { - uri: 'https://www.w3.org/2018/credentials#VerifiableCredential', - }, - { - uri: 'https://w3id.org/citizenship#PermanentResident', - }, - { - uri: 'https://w3id.org/citizenship/v1', - }, - ], - name: "EU Driver's License", - group: ['A'], - id: 'citizenship_input_1', -} diff --git a/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts b/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts deleted file mode 100644 index a81e4e5553..0000000000 --- a/packages/core/src/modules/proofs/formats/errors/InvalidEncodedValueError.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' - -export class InvalidEncodedValueError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts b/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts deleted file mode 100644 index a00abc40cb..0000000000 --- a/packages/core/src/modules/proofs/formats/errors/MissingIndyProofMessageError.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { AriesFrameworkError } from '../../../../error/AriesFrameworkError' - -export class MissingIndyProofMessageError extends AriesFrameworkError {} diff --git a/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts b/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts deleted file mode 100644 index db6435670c..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/__tests__/util.test.ts +++ /dev/null @@ -1,541 +0,0 @@ -import type { default as Indy } from 'indy-sdk' - -import { AriesFrameworkError } from '../../../../../error' -import { AttributeFilter, PredicateType, ProofAttributeInfo, ProofPredicateInfo, ProofRequest } from '../models' -import { areIndyProofRequestsEqual, assertNoDuplicateGroupsNamesInProofRequest } from '../util' - -const proofRequest = { - name: 'Proof Request', - version: '1.0.0', - nonce: 'nonce', - ver: '1.0', - non_revoked: {}, - requested_attributes: { - a: { - names: ['name1', 'name2'], - restrictions: [ - { - cred_def_id: 'cred_def_id1', - }, - { - schema_id: 'schema_id', - }, - ], - }, - }, - requested_predicates: { - p: { - name: 'Hello', - p_type: '<', - p_value: 10, - restrictions: [ - { - cred_def_id: 'string2', - }, - { - cred_def_id: 'string', - }, - ], - }, - }, -} satisfies Indy.IndyProofRequest - -const credDefId = '9vPXgSpQJPkJEALbLXueBp:3:CL:57753:tag1' -const nonce = 'testtesttest12345' - -describe('IndyProofFormat | util', () => { - describe('assertNoDuplicateGroupsNamesInProofRequest', () => { - test('attribute names match', () => { - const attributes = { - age1: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - age2: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const proofRequest = new ProofRequest({ - name: 'proof-request', - version: '1.0', - nonce, - requestedAttributes: attributes, - }) - - expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).not.toThrow() - }) - - test('attribute names match with predicates name', () => { - const attributes = { - attrib: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - predicate: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const proofRequest = new ProofRequest({ - name: 'proof-request', - version: '1.0', - nonce, - requestedAttributes: attributes, - requestedPredicates: predicates, - }) - - expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).toThrowError(AriesFrameworkError) - }) - }) - describe('areIndyProofRequestsEqual', () => { - test('does not compare name, ver, version and nonce', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - name: 'Proof Request 2', - version: '2.0.0', - nonce: 'nonce2', - ver: '2.0', - }) - ).toBe(true) - }) - - test('check top level non_revocation interval', () => { - // empty object is semantically equal to undefined - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - non_revoked: {}, - }) - ).toBe(true) - - // properties inside object are different - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - non_revoked: { - to: 5, - }, - }, - { - ...proofRequest, - non_revoked: { - from: 5, - }, - } - ) - ).toBe(false) - - // One has non_revoked, other doesn't - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - non_revoked: { - from: 5, - }, - }) - ).toBe(false) - }) - - test('ignores attribute group name differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - b: proofRequest.requested_attributes.a, - }, - }) - ).toBe(true) - }) - - test('ignores attribute restriction order', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - restrictions: [...proofRequest.requested_attributes.a.restrictions].reverse(), - }, - }, - }) - ).toBe(true) - }) - - test('ignores attribute restriction undefined vs empty array', () => { - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - restrictions: undefined, - }, - }, - }, - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - restrictions: [], - }, - }, - } - ) - ).toBe(true) - }) - - test('ignores attribute names order', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - names: ['name2', 'name1'], - }, - }, - }) - ).toBe(true) - }) - - test('checks attribute non_revocation interval', () => { - // empty object is semantically equal to undefined - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - non_revoked: {}, - }, - }, - }) - ).toBe(true) - - // properties inside object are different - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - non_revoked: { - to: 5, - }, - }, - }, - }, - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - non_revoked: { - from: 5, - }, - }, - }, - } - ) - ).toBe(false) - - // One has non_revoked, other doesn't - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - non_revoked: { - from: 5, - }, - }, - }, - }) - ).toBe(false) - }) - - test('checks attribute restriction differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - restrictions: [ - { - cred_def_id: 'cred_def_id1', - }, - { - cred_def_id: 'cred_def_id2', - }, - ], - }, - }, - }) - ).toBe(false) - }) - - test('checks attribute name differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - names: ['name3'], - }, - }, - }) - ).toBe(false) - - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - name: 'name3', - names: undefined, - }, - }, - }) - ).toBe(false) - }) - - test('allows names with one value to be same as name property', () => { - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - name: 'name1', - }, - }, - }, - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - names: ['name1'], - }, - }, - } - ) - ).toBe(true) - - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - name: 'name1', - }, - }, - }, - { - ...proofRequest, - requested_attributes: { - a: { - ...proofRequest.requested_attributes.a, - names: ['name2'], - }, - }, - } - ) - ).toBe(false) - }) - - test('ignores predicate group name differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - a: proofRequest.requested_predicates.p, - }, - }) - ).toBe(true) - }) - - test('ignores predicate restriction order', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - restrictions: [...proofRequest.requested_predicates.p.restrictions].reverse(), - }, - }, - }) - ).toBe(true) - }) - - test('ignores predicate restriction undefined vs empty array', () => { - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - restrictions: undefined, - }, - }, - }, - { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - restrictions: [], - }, - }, - } - ) - ).toBe(true) - }) - - test('checks predicate restriction differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_attributes: { - p: { - ...proofRequest.requested_predicates.p, - restrictions: [ - { - cred_def_id: 'cred_def_id1', - }, - { - cred_def_id: 'cred_def_id2', - }, - ], - }, - }, - }) - ).toBe(false) - }) - - test('checks predicate name differences', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - name: 'name3', - }, - }, - }) - ).toBe(false) - }) - - test('checks predicate non_revocation interval', () => { - // empty object is semantically equal to undefined - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - non_revoked: {}, - }, - }, - }) - ).toBe(true) - - // properties inside object are different - expect( - areIndyProofRequestsEqual( - { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - non_revoked: { - to: 5, - }, - }, - }, - }, - { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - non_revoked: { - from: 5, - }, - }, - }, - } - ) - ).toBe(false) - - // One has non_revoked, other doesn't - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - non_revoked: { - from: 5, - }, - }, - }, - }) - ).toBe(false) - }) - - test('checks predicate p_type and p_value', () => { - expect( - areIndyProofRequestsEqual(proofRequest, { - ...proofRequest, - requested_predicates: { - p: { - ...proofRequest.requested_predicates.p, - p_type: '<', - p_value: 134134, - }, - }, - }) - ).toBe(false) - }) - }) -}) diff --git a/packages/core/src/modules/proofs/formats/indy/util.ts b/packages/core/src/modules/proofs/formats/indy/util.ts deleted file mode 100644 index f1c3df2a16..0000000000 --- a/packages/core/src/modules/proofs/formats/indy/util.ts +++ /dev/null @@ -1,266 +0,0 @@ -import type { V1PresentationPreviewAttributeOptions, V1PresentationPreviewPredicateOptions } from '../../protocol' -import type { default as Indy } from 'indy-sdk' - -import { AriesFrameworkError } from '../../../../error' -import { areObjectsEqual } from '../../../../utils' -import { uuid } from '../../../../utils/uuid' - -import { ProofAttributeInfo, ProofPredicateInfo, ProofRequest } from './models' - -export function createRequestFromPreview({ - name, - version, - nonce, - attributes, - predicates, -}: { - name: string - version: string - nonce: string - attributes: V1PresentationPreviewAttributeOptions[] - predicates: V1PresentationPreviewPredicateOptions[] -}): ProofRequest { - const proofRequest = new ProofRequest({ - name, - version, - nonce, - }) - - /** - * Create mapping of attributes by referent. This required the - * attributes to come from the same credential. - * @see https://github.com/hyperledger/aries-rfcs/blob/master/features/0037-present-proof/README.md#referent - * - * { - * "referent1": [Attribute1, Attribute2], - * "referent2": [Attribute3] - * } - */ - const attributesByReferent: Record = {} - for (const proposedAttributes of attributes ?? []) { - if (!proposedAttributes.referent) proposedAttributes.referent = uuid() - - const referentAttributes = attributesByReferent[proposedAttributes.referent] - - // Referent key already exist, add to list - if (referentAttributes) { - referentAttributes.push(proposedAttributes) - } - - // Referent key does not exist yet, create new entry - else { - attributesByReferent[proposedAttributes.referent] = [proposedAttributes] - } - } - - // Transform attributes by referent to requested attributes - for (const [referent, proposedAttributes] of Object.entries(attributesByReferent)) { - // Either attributeName or attributeNames will be undefined - const attributeName = proposedAttributes.length === 1 ? proposedAttributes[0].name : undefined - const attributeNames = proposedAttributes.length > 1 ? proposedAttributes.map((a) => a.name) : undefined - - const requestedAttribute = new ProofAttributeInfo({ - name: attributeName, - names: attributeNames, - restrictions: [ - { - credentialDefinitionId: proposedAttributes[0].credentialDefinitionId, - }, - ], - }) - - proofRequest.requestedAttributes.set(referent, requestedAttribute) - } - - // Transform proposed predicates to requested predicates - for (const proposedPredicate of predicates ?? []) { - const requestedPredicate = new ProofPredicateInfo({ - name: proposedPredicate.name, - predicateType: proposedPredicate.predicate, - predicateValue: proposedPredicate.threshold, - restrictions: [ - { - credentialDefinitionId: proposedPredicate.credentialDefinitionId, - }, - ], - }) - - proofRequest.requestedPredicates.set(uuid(), requestedPredicate) - } - - return proofRequest -} - -/** - * Checks whether two `names` arrays are equal. The order of the names doesn't matter. - */ -function areNamesEqual({ - nameA, - namesA, - nameB, - namesB, -}: { - namesA?: string[] - nameA?: string - namesB?: string[] - nameB?: string -}) { - const namesACombined = nameA ? [nameA] : namesA - const namesBCombined = nameB ? [nameB] : namesB - - // Filter out case where both are not set (invalid) - if (!namesACombined || !namesBCombined) return false - - // Check if there are any duplicates - if (new Set(namesACombined).size !== namesACombined.length || new Set(namesBCombined).size !== namesBCombined.length) - return false - - // Check if the number of names is equal between A & B - if (namesACombined.length !== namesBCombined.length) return false - - return namesACombined.every((a) => namesBCombined.includes(a)) -} - -/** - * Checks whether two proof requests are semantically equal. The `name`, `version` and `nonce`, `ver` fields are ignored. - * In addition the group names don't have to be the same between the different requests. - */ -export function areIndyProofRequestsEqual(requestA: Indy.IndyProofRequest, requestB: Indy.IndyProofRequest): boolean { - // Check if the top-level non-revocation interval is equal - if (!isNonRevokedEqual(requestA.non_revoked, requestB.non_revoked)) return false - - const attributeAList = Object.values(requestA.requested_attributes) - const attributeBList = Object.values(requestB.requested_attributes) - - // Check if the number of attribute groups is equal in both requests - if (attributeAList.length !== attributeBList.length) return false - - const predicatesA = Object.values(requestA.requested_predicates) - const predicatesB = Object.values(requestB.requested_predicates) - - if (predicatesA.length !== predicatesB.length) return false - - // Check if all attribute groups in A are also in B - const attributesMatch = attributeAList.every((a) => { - // find an attribute in B that matches this attribute - const bIndex = attributeBList.findIndex((b) => { - return ( - // Check if name and names are equal - areNamesEqual({ - nameB: b.name, - namesB: b.names, - nameA: a.name, - namesA: a.names, - }) && - isNonRevokedEqual(a.non_revoked, b.non_revoked) && - areRestrictionsEqual(a.restrictions, b.restrictions) - ) - }) - - // Match found - if (bIndex !== -1) { - attributeBList.splice(bIndex, 1) - return true - } - - // Match not found - return false - }) - - if (!attributesMatch) return false - - const predicatesMatch = predicatesA.every((a) => { - // find a predicate in B that matches this predicate - const bIndex = predicatesB.findIndex((b) => { - return ( - a.name === b.name && - a.p_type === b.p_type && - a.p_value === b.p_value && - isNonRevokedEqual(a.non_revoked, b.non_revoked) && - areRestrictionsEqual(a.restrictions, b.restrictions) - ) - }) - - if (bIndex !== -1) { - predicatesB.splice(bIndex, 1) - return true - } - - return false - }) - - if (!predicatesMatch) return false - - return true -} - -/** - * Checks whether two non-revocation intervals are semantically equal. They are considered equal if: - * - Both are undefined - * - Both are empty objects - * - One if undefined and the other is an empty object - * - Both have the same from and to values - */ -function isNonRevokedEqual(nonRevokedA?: Indy.NonRevokedInterval, nonRevokedB?: Indy.NonRevokedInterval) { - // Having an empty non-revoked object is the same as not having one - if (nonRevokedA === undefined) - return nonRevokedB === undefined || (nonRevokedB.from === undefined && nonRevokedB.to === undefined) - if (nonRevokedB === undefined) return nonRevokedA.from === undefined && nonRevokedA.to === undefined - - return nonRevokedA.from === nonRevokedB.from && nonRevokedA.to === nonRevokedB.to -} - -/** - * Check if two restriction lists are equal. The order of the restrictions does not matter. - */ -function areRestrictionsEqual(restrictionsA?: Indy.WalletQuery[], restrictionsB?: Indy.WalletQuery[]) { - // Having an undefined restrictions property or an empty array is the same - if (restrictionsA === undefined) return restrictionsB === undefined || restrictionsB.length === 0 - if (restrictionsB === undefined) return restrictionsA.length === 0 - - // Clone array to not modify input object - const bList = [...restrictionsB] - - // Check if all restrictions in A are also in B - return restrictionsA.every((a) => { - const bIndex = restrictionsB.findIndex((b) => areObjectsEqual(a, b)) - - // Match found - if (bIndex !== -1) { - bList.splice(bIndex, 1) - return true - } - - // Match not found - return false - }) -} - -function attributeNamesToArray(proofRequest: ProofRequest) { - // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array - // containing all attribute names from the requested attributes. - return Array.from(proofRequest.requestedAttributes.values()).reduce( - (names, a) => [...names, ...(a.name ? [a.name] : a.names ? a.names : [])], - [] - ) -} - -function predicateNamesToArray(proofRequest: ProofRequest) { - return Array.from(new Set(Array.from(proofRequest.requestedPredicates.values()).map((a) => a.name))) -} - -function assertNoDuplicates(predicates: string[], attributeNames: string[]) { - const duplicates = predicates.filter((item) => attributeNames.indexOf(item) !== -1) - if (duplicates.length > 0) { - throw new AriesFrameworkError( - `The proof request contains duplicate predicates and attributes: ${duplicates.toString()}` - ) - } -} - -// TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. -export function assertNoDuplicateGroupsNamesInProofRequest(proofRequest: ProofRequest) { - const attributes = attributeNamesToArray(proofRequest) - const predicates = predicateNamesToArray(proofRequest) - assertNoDuplicates(predicates, attributes) -} diff --git a/packages/core/src/modules/proofs/protocol/index.ts b/packages/core/src/modules/proofs/protocol/index.ts index db72d7287c..71799a5c45 100644 --- a/packages/core/src/modules/proofs/protocol/index.ts +++ b/packages/core/src/modules/proofs/protocol/index.ts @@ -1,6 +1,10 @@ -export * from './v1' export * from './v2' -export { ProofProtocol } from './ProofProtocol' import * as ProofProtocolOptions from './ProofProtocolOptions' +export { ProofProtocol } from './ProofProtocol' +// NOTE: ideally we don't export the BaseProofProtocol, but as the V1ProofProtocol is defined in the +// anoncreds package, we need to export it. We should at some point look at creating a core package which can be used for +// sharing internal types, and when you want to build you own modules, and an agent package, which is the one you use when +// consuming the framework +export { BaseProofProtocol } from './BaseProofProtocol' export { ProofProtocolOptions } diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts index 815188bf69..91c389b4d1 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts @@ -1,29 +1,72 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { V1PresentationPreview } from '../../v1' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { ReplaySubject } from 'rxjs' + +import { + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' -import { getGroupKeysFromIndyProofFormatData } from '../../../formats/indy/__tests__/groupKeys' -import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' import { ProofState } from '../../../models' import { ProofExchangeRecord } from '../../../repository' import { V2ProposePresentationMessage, V2RequestPresentationMessage, V2PresentationMessage } from '../messages' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let aliceConnection: ConnectionRecord - let faberConnection: ConnectionRecord + let faberAgent: AnonCredsTestsAgent + let faberReplay: ReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: ReplaySubject + let credentialDefinitionId: string + let aliceConnectionId: string + let faberConnectionId: string let faberProofExchangeRecord: ProofExchangeRecord let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: V1PresentationPreview beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent indy proofs', 'Alice agent indy proofs')) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent indy proofs', + holderName: 'Alice agent indy proofs', + attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '22', + }, + { + name: 'profile_picture', + value: 'https://example.com/alice.jpg', + }, + { + name: 'x-ray', + value: 'https://example.com/alice-xray.jpg', + }, + ], + }, + }) }) afterAll(async () => { @@ -43,14 +86,27 @@ describe('Present Proof', () => { }) aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', proofFormats: { indy: { name: 'abc', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'Alice', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 18, + credentialDefinitionId, + }, + ], }, }, }) @@ -217,9 +273,6 @@ describe('Present Proof', () => { const formatData = await aliceAgent.proofs.getFormatData(aliceProofExchangeRecord.id) - // eslint-disable-next-line prefer-const - let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) - expect(formatData).toMatchObject({ proposal: { indy: { @@ -230,23 +283,23 @@ describe('Present Proof', () => { 0: { name: 'name', }, - [proposeKey1]: { + anything: { name: 'image_0', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [proposeKey2]: { + else: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -262,23 +315,23 @@ describe('Present Proof', () => { 0: { name: 'name', }, - [requestKey1]: { + anything: { name: 'image_0', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [requestKey2]: { + else: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -307,39 +360,6 @@ describe('Present Proof', () => { }) test('Faber starts with proof request to Alice', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) @@ -348,13 +368,41 @@ describe('Present Proof', () => { testLogger.test('Faber sends a presentation request to Alice') faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'Proof Request', version: '1.0.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 18, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -476,39 +524,6 @@ describe('Present Proof', () => { }) test('Alice provides credentials via call to getRequestedCredentials', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) @@ -517,13 +532,41 @@ describe('Present Proof', () => { testLogger.test('Faber sends a presentation request to Alice') faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'Proof Request', version: '1.0.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -605,39 +648,6 @@ describe('Present Proof', () => { }) test('Faber starts with proof request to Alice but gets Problem Reported', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) @@ -646,13 +656,41 @@ describe('Present Proof', () => { testLogger.test('Faber sends a presentation request to Alice') faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v2', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) diff --git a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts index de6763884f..14ec7c22b8 100644 --- a/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/RoutingService.test.ts @@ -1,40 +1,39 @@ +import type { Wallet } from '../../../../wallet' + import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../../tests/helpers' import { EventEmitter } from '../../../../agent/EventEmitter' import { Key } from '../../../../crypto' -import { IndyWallet } from '../../../../wallet/IndyWallet' import { RoutingEventTypes } from '../../RoutingEvents' import { MediationRecipientService } from '../MediationRecipientService' import { RoutingService } from '../RoutingService' -jest.mock('../../../../wallet/IndyWallet') -const IndyWalletMock = IndyWallet as jest.Mock - jest.mock('../MediationRecipientService') const MediationRecipientServiceMock = MediationRecipientService as jest.Mock +const recipientKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') const agentConfig = getAgentConfig('RoutingService', { endpoints: ['http://endpoint.com'], }) const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) -const wallet = new IndyWalletMock() +const wallet = { + createKey: jest.fn().mockResolvedValue(recipientKey), + // with satisfies Partial we still get type errors when the interface changes +} satisfies Partial const agentContext = getAgentContext({ - wallet, + wallet: wallet as unknown as Wallet, agentConfig, }) const mediationRecipientService = new MediationRecipientServiceMock() const routingService = new RoutingService(mediationRecipientService, eventEmitter) -const recipientKey = Key.fromFingerprint('z6Mkk7yqnGF3YwTrLpqrW6PGsKci7dNqh1CjnvMbzrMerSeL') - const routing = { endpoints: ['http://endpoint.com'], recipientKey, routingKeys: [], } mockFunction(mediationRecipientService.addMediationRouting).mockResolvedValue(routing) -mockFunction(wallet.createKey).mockResolvedValue(recipientKey) describe('RoutingService', () => { afterEach(() => { diff --git a/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts b/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts index 067a290dcb..1329595f56 100644 --- a/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts +++ b/packages/core/src/storage/__tests__/DidCommMessageRepository.test.ts @@ -1,15 +1,17 @@ +import type { StorageService } from '../StorageService' + import { Subject } from 'rxjs' +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' import { getAgentConfig, getAgentContext, mockFunction } from '../../../tests/helpers' import { EventEmitter } from '../../agent/EventEmitter' import { ConnectionInvitationMessage } from '../../modules/connections' import { JsonTransformer } from '../../utils/JsonTransformer' -import { IndyStorageService } from '../IndyStorageService' import { DidCommMessageRecord, DidCommMessageRepository, DidCommMessageRole } from '../didcomm' -jest.mock('../IndyStorageService') +jest.mock('../../../../../tests/InMemoryStorageService') -const StorageMock = IndyStorageService as unknown as jest.Mock> +const StorageMock = InMemoryStorageService as unknown as jest.Mock> const invitationJson = { '@type': 'https://didcomm.org/connections/1.0/invitation', @@ -24,7 +26,7 @@ const agentContext = getAgentContext() describe('DidCommMessageRepository', () => { let repository: DidCommMessageRepository - let storageMock: IndyStorageService + let storageMock: StorageService let eventEmitter: EventEmitter beforeEach(async () => { diff --git a/packages/core/src/storage/__tests__/Repository.test.ts b/packages/core/src/storage/__tests__/Repository.test.ts index 11cb3dd9cc..a5ae5fd52f 100644 --- a/packages/core/src/storage/__tests__/Repository.test.ts +++ b/packages/core/src/storage/__tests__/Repository.test.ts @@ -1,27 +1,28 @@ import type { AgentContext } from '../../agent' import type { TagsBase } from '../BaseRecord' import type { RecordDeletedEvent, RecordSavedEvent, RecordUpdatedEvent } from '../RepositoryEvents' +import type { StorageService } from '../StorageService' import { Subject } from 'rxjs' +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' import { getAgentConfig, getAgentContext, mockFunction } from '../../../tests/helpers' import { EventEmitter } from '../../agent/EventEmitter' import { AriesFrameworkError, RecordDuplicateError, RecordNotFoundError } from '../../error' -import { IndyStorageService } from '../IndyStorageService' import { Repository } from '../Repository' import { RepositoryEventTypes } from '../RepositoryEvents' import { TestRecord } from './TestRecord' -jest.mock('../IndyStorageService') +jest.mock('../../../../../tests/InMemoryStorageService') -const StorageMock = IndyStorageService as unknown as jest.Mock> +const StorageMock = InMemoryStorageService as unknown as jest.Mock> const config = getAgentConfig('Repository') describe('Repository', () => { let repository: Repository - let storageMock: IndyStorageService + let storageMock: StorageService let agentContext: AgentContext let eventEmitter: EventEmitter diff --git a/packages/core/src/utils/__tests__/regex.test.ts b/packages/core/src/utils/__tests__/regex.test.ts deleted file mode 100644 index 93cbaa7ae8..0000000000 --- a/packages/core/src/utils/__tests__/regex.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { credDefIdRegex, indyDidRegex, schemaIdRegex, schemaVersionRegex } from '../regex' - -describe('Valid Regular Expression', () => { - const invalidTest = 'test' - - test('test for credDefIdRegex', async () => { - const test = 'q7ATwTYbQDgiigVijUAej:3:CL:160971:1.0.0' - expect(test).toMatch(credDefIdRegex) - expect(credDefIdRegex.test(invalidTest)).toBeFalsy() - }) - - test('test for indyDidRegex', async () => { - const test = 'did:sov:q7ATwTYbQDgiigVijUAej' - expect(test).toMatch(indyDidRegex) - expect(indyDidRegex.test(invalidTest)).toBeFalsy - }) - - test('test for schemaIdRegex', async () => { - const test = 'q7ATwTYbQDgiigVijUAej:2:test:1.0' - expect(test).toMatch(schemaIdRegex) - expect(schemaIdRegex.test(invalidTest)).toBeFalsy - }) - - test('test for schemaVersionRegex', async () => { - const test = '1.0.0' - expect(test).toMatch(schemaVersionRegex) - expect(schemaVersionRegex.test(invalidTest)).toBeFalsy - }) -}) diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 58edb19a94..a432b6bc6c 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -5,7 +5,6 @@ export * from './MultiBaseEncoder' export * from './buffer' export * from './MultiHashEncoder' export * from './JWE' -export * from './regex' export * from './VarintEncoder' export * from './Hasher' export * from './validators' diff --git a/packages/core/src/utils/regex.ts b/packages/core/src/utils/regex.ts deleted file mode 100644 index 629be026df..0000000000 --- a/packages/core/src/utils/regex.ts +++ /dev/null @@ -1,4 +0,0 @@ -export const schemaIdRegex = /^[a-zA-Z0-9]{21,22}:2:.+:[0-9.]+$/ -export const schemaVersionRegex = /^(\d+\.)?(\d+\.)?(\*|\d+)$/ -export const credDefIdRegex = /^([a-zA-Z0-9]{21,22}):3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+)):(.+)?$/ -export const indyDidRegex = /^(did:sov:)?[a-zA-Z0-9]{21,22}$/ diff --git a/packages/core/src/utils/transformers.ts b/packages/core/src/utils/transformers.ts index eb6dea844a..005f0065da 100644 --- a/packages/core/src/utils/transformers.ts +++ b/packages/core/src/utils/transformers.ts @@ -6,45 +6,6 @@ import { DateTime } from 'luxon' import { Metadata } from '../storage/Metadata' -import { JsonTransformer } from './JsonTransformer' - -/** - * Decorator that transforms json to and from corresponding record. - * - * @example - * class Example { - * RecordTransformer(Service) - * private services: Record; - * } - */ -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export function RecordTransformer(Class: { new (...args: any[]): T }) { - return Transform(({ value, type }) => { - switch (type) { - case TransformationType.CLASS_TO_PLAIN: - return Object.entries(value).reduce( - (accumulator, [key, attribute]) => ({ - ...accumulator, - [key]: JsonTransformer.toJSON(attribute), - }), - {} - ) - - case TransformationType.PLAIN_TO_CLASS: - return Object.entries(value).reduce( - (accumulator, [key, attribute]) => ({ - ...accumulator, - [key]: JsonTransformer.fromJSON(attribute, Class), - }), - {} - ) - - default: - return value - } - }) -} - /* * Decorator that transforms to and from a metadata instance. */ diff --git a/packages/core/src/utils/type.ts b/packages/core/src/utils/type.ts index 2155975323..064ca0ce75 100644 --- a/packages/core/src/utils/type.ts +++ b/packages/core/src/utils/type.ts @@ -4,10 +4,6 @@ export type SingleOrArray = T | T[] export type Optional = Pick, K> & Omit -export const isString = (value: unknown): value is string => typeof value === 'string' -export const isNumber = (value: unknown): value is number => typeof value === 'number' -export const isBoolean = (value: unknown): value is boolean => typeof value === 'boolean' - export const isJsonObject = (value: unknown): value is JsonObject => { return value !== undefined && typeof value === 'object' && value !== null && !Array.isArray(value) } diff --git a/packages/core/src/wallet/util/assertIndyWallet.ts b/packages/core/src/wallet/util/assertIndyWallet.ts deleted file mode 100644 index a26c43f0fe..0000000000 --- a/packages/core/src/wallet/util/assertIndyWallet.ts +++ /dev/null @@ -1,12 +0,0 @@ -import type { Wallet } from '../Wallet' - -import { AriesFrameworkError } from '../../error' -import { IndyWallet } from '../IndyWallet' - -export function assertIndyWallet(wallet: Wallet): asserts wallet is IndyWallet { - if (!(wallet instanceof IndyWallet)) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const walletClassName = (wallet as any).constructor?.name ?? 'unknown' - throw new AriesFrameworkError(`Expected wallet to be instance of IndyWallet, found ${walletClassName}`) - } -} diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index f70e283221..46b05f80c4 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -1,24 +1,20 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { CreateCredentialOfferOptions, DefaultCredentialProtocols } from '../src/modules/credentials' +import type { V1CredentialProtocol } from '../../anoncreds/src' +import type { CreateCredentialOfferOptions } from '../src/modules/credentials' import type { AgentMessage, AgentMessageReceivedEvent } from '@aries-framework/core' import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getLegacyAnonCredsModules, prepareForAnonCredsIssuance } from '../../anoncreds/tests/legacyAnonCredsSetup' import { Agent } from '../src/agent/Agent' -import { Key } from '../src/crypto' -import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' -import { - AgentEventTypes, - AriesFrameworkError, - AutoAcceptCredential, - CredentialState, - V1CredentialPreview, -} from '@aries-framework/core' +import { AgentEventTypes, AriesFrameworkError, AutoAcceptCredential, CredentialState } from '@aries-framework/core' +import { Key } from '../src/crypto' +import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' import { OutOfBandDidCommService } from '../src/modules/oob/domain/OutOfBandDidCommService' import { OutOfBandEventTypes } from '../src/modules/oob/domain/OutOfBandEvents' import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' @@ -28,14 +24,24 @@ import { DidCommMessageRepository, DidCommMessageRole } from '../src/storage' import { JsonEncoder } from '../src/utils' import { TestMessage } from './TestMessage' -import { getAgentOptions, prepareForIndyIssuance, waitForCredentialRecord } from './helpers' - -const faberAgentOptions = getAgentOptions('Faber Agent OOB', { - endpoints: ['rxjs:faber'], -}) -const aliceAgentOptions = getAgentOptions('Alice Agent OOB', { - endpoints: ['rxjs:alice'], -}) +import { getAgentOptions, waitForCredentialRecord } from './helpers' + +const legacyAnonCredsModules = getLegacyAnonCredsModules() + +const faberAgentOptions = getAgentOptions( + 'Faber Agent OOB', + { + endpoints: ['rxjs:faber'], + }, + legacyAnonCredsModules +) +const aliceAgentOptions = getAgentOptions( + 'Alice Agent OOB', + { + endpoints: ['rxjs:alice'], + }, + legacyAnonCredsModules +) describe('out of band', () => { const makeConnectionConfig = { @@ -56,9 +62,9 @@ describe('out of band', () => { autoAcceptConnection: false, } - let faberAgent: Agent - let aliceAgent: Agent - let credentialTemplate: CreateCredentialOfferOptions + let faberAgent: Agent + let aliceAgent: Agent + let credentialTemplate: CreateCredentialOfferOptions<[V1CredentialProtocol]> beforeAll(async () => { const faberMessages = new Subject() @@ -79,19 +85,34 @@ describe('out of band', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age', 'profile_picture', 'x-ray']) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], + issuerId: faberAgent.publicDid?.did as string, + }) credentialTemplate = { protocolVersion: 'v1', credentialFormats: { indy: { - attributes: V1CredentialPreview.fromRecord({ - name: 'name', - age: 'age', - profile_picture: 'profile_picture', - 'x-ray': 'x-ray', - }).attributes, - credentialDefinitionId: definition.id, + attributes: [ + { + name: 'name', + value: 'name', + }, + { + name: 'age', + value: 'age', + }, + { + name: 'profile_picture', + value: 'profile_picture', + }, + { + name: 'x-ray', + value: 'x-ray', + }, + ], + credentialDefinitionId: credentialDefinition.credentialDefinitionId, }, }, autoAcceptCredential: AutoAcceptCredential.Never, diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index 45fa30e713..03be108090 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -1,33 +1,35 @@ -import type { Agent, ConnectionRecord, ProofExchangeRecord } from '../src' -import type { V1PresentationPreview } from '../src/modules/proofs/protocol/v1/models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' - -import { - ProofAttributeInfo, - AttributeFilter, - ProofPredicateInfo, - PredicateType, -} from '../src/modules/proofs/formats/indy/models' +import type { V1PresentationPreview } from '../../anoncreds/src' +import type { AnonCredsTestsAgent } from '../../anoncreds/tests/legacyAnonCredsSetup' +import type { ProofExchangeRecord } from '../src' + +import { setupAnonCredsTests } from '../../anoncreds/tests/legacyAnonCredsSetup' import { ProofState } from '../src/modules/proofs/models/ProofState' import { uuid } from '../src/utils/uuid' -import { setupProofsTest, waitForProofExchangeRecord } from './helpers' +import { waitForProofExchangeRecord } from './helpers' import testLogger from './logger' describe('Present Proof Subprotocol', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let credentialDefinitionId: string + let faberConnectionId: string + let aliceConnectionId: string let aliceProofExchangeRecord: ProofExchangeRecord let presentationPreview: V1PresentationPreview beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent', 'Alice agent')) - testLogger.test('Issuing second credential') + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent', + holderName: 'Alice agent', + })) }) afterAll(async () => { @@ -50,7 +52,7 @@ describe('Present Proof Subprotocol', () => { }) aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', parentThreadId, proofFormats: { @@ -118,30 +120,6 @@ describe('Present Proof Subprotocol', () => { const parentThreadId = uuid() testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { parentThreadId, state: ProofState.RequestReceived, @@ -150,15 +128,35 @@ describe('Present Proof Subprotocol', () => { // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') const faberProofExchangeRecord = await faberAgent.proofs.requestProof({ - connectionId: faberConnection.id, + connectionId: faberConnectionId, parentThreadId, protocolVersion: 'v1', proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -217,7 +215,7 @@ describe('Present Proof Subprotocol', () => { }) aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', parentThreadId, proofFormats: { @@ -285,30 +283,6 @@ describe('Present Proof Subprotocol', () => { const parentThreadId = uuid() testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { parentThreadId, state: ProofState.RequestReceived, @@ -317,15 +291,35 @@ describe('Present Proof Subprotocol', () => { // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') const faberProofExchangeRecord = await faberAgent.proofs.requestProof({ - connectionId: faberConnection.id, + connectionId: faberConnectionId, parentThreadId, protocolVersion: 'v2', proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) diff --git a/packages/indy-sdk/package.json b/packages/indy-sdk/package.json index d996bcd771..7e9f09af5a 100644 --- a/packages/indy-sdk/package.json +++ b/packages/indy-sdk/package.json @@ -33,6 +33,7 @@ "tsyringe": "^4.7.0" }, "devDependencies": { + "@stablelib/ed25519": "^1.0.3", "rimraf": "^4.0.7", "typescript": "~4.9.4" } diff --git a/packages/indy-sdk/src/IndySdkModule.ts b/packages/indy-sdk/src/IndySdkModule.ts index 2d52238b7d..a0858c23bb 100644 --- a/packages/indy-sdk/src/IndySdkModule.ts +++ b/packages/indy-sdk/src/IndySdkModule.ts @@ -25,6 +25,9 @@ export class IndySdkModule implements Module { public register(dependencyManager: DependencyManager) { dependencyManager.registerInstance(IndySdkSymbol, this.config.indySdk) + // Register config + dependencyManager.registerInstance(IndySdkModuleConfig, this.config) + // NOTE: for now we are registering the needed indy services. We may want to make this // more explicit and require the user to register the services they need on the specific modules. dependencyManager.registerSingleton(InjectionSymbols.Wallet, IndySdkWallet) diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts index 2ee2a64a3b..32da71e3ac 100644 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts @@ -9,11 +9,11 @@ import { EventEmitter, RepositoryEventTypes, } from '@aries-framework/core' -import { mockFunction, getAgentConfig, getAgentContext, agentDependencies } from '@aries-framework/core/tests/helpers' import indySdk from 'indy-sdk' import { Subject } from 'rxjs' import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { mockFunction, getAgentConfig, getAgentContext, agentDependencies } from '../../../../core/tests/helpers' import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' import { IndySdkSymbol } from '../../types' import { IndySdkWallet } from '../../wallet' diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts index 71a969f74e..c9bb1bbc93 100644 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts @@ -3,10 +3,10 @@ import type { IndyEndpointAttrib } from '../didSovUtil' import type { GetNymResponse } from 'indy-sdk' import { SigningProviderRegistry, JsonTransformer } from '@aries-framework/core' -import { parseDid } from '@aries-framework/core/src/modules/dids/domain/parse' -import { mockFunction, getAgentConfig, getAgentContext } from '@aries-framework/core/tests/helpers' import indySdk from 'indy-sdk' +import { parseDid } from '../../../../core/src/modules/dids/domain/parse' +import { mockFunction, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' import { IndySdkSymbol } from '../../types' import { IndySdkWallet } from '../../wallet' diff --git a/packages/indy-sdk/src/ledger/IndySdkPool.ts b/packages/indy-sdk/src/ledger/IndySdkPool.ts index cb19dd3052..d7720ad44e 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPool.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPool.ts @@ -54,7 +54,7 @@ export class IndySdkPool { } public get didIndyNamespace(): string { - return this.didIndyNamespace + return this.config.indyNamespace } public get config() { diff --git a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts index f2d9a63c46..f4768837b7 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts @@ -86,7 +86,7 @@ export class IndySdkPoolService { // one or more of the ledgers returned an unknown error throw new IndySdkPoolError( - `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers`, + `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers. ${rejectedOtherThanNotFound[0].reason}`, { cause: rejectedOtherThanNotFound[0].reason } ) } diff --git a/packages/indy-sdk/src/wallet/IndySdkWallet.ts b/packages/indy-sdk/src/wallet/IndySdkWallet.ts index 9230ed5f28..f5c5b6257d 100644 --- a/packages/indy-sdk/src/wallet/IndySdkWallet.ts +++ b/packages/indy-sdk/src/wallet/IndySdkWallet.ts @@ -447,7 +447,7 @@ export class IndySdkWallet implements Wallet { } } - public async createDid(didConfig?: DidConfig): Promise { + private async createDid(didConfig?: DidConfig): Promise { try { const [did, verkey] = await this.indySdk.createAndStoreMyDid(this.handle, didConfig || {}) diff --git a/packages/indy-sdk/tests/setupIndySdkModule.ts b/packages/indy-sdk/tests/setupIndySdkModule.ts new file mode 100644 index 0000000000..dad0fafd47 --- /dev/null +++ b/packages/indy-sdk/tests/setupIndySdkModule.ts @@ -0,0 +1,25 @@ +import { DidsModule, utils } from '@aries-framework/core' +import indySdk from 'indy-sdk' + +import { genesisPath, taaVersion, taaAcceptanceMechanism } from '../../core/tests/helpers' +import { IndySdkModule, IndySdkSovDidRegistrar, IndySdkSovDidResolver } from '../src' + +export const getIndySdkModules = ({ + indyNamespace = `localhost-${utils.uuid()}`, +}: { indyNamespace?: string } = {}) => ({ + indySdk: new IndySdkModule({ + indySdk, + networks: [ + { + isProduction: false, + genesisPath, + indyNamespace, + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, + }, + ], + }), + dids: new DidsModule({ + registrars: [new IndySdkSovDidRegistrar()], + resolvers: [new IndySdkSovDidResolver()], + }), +}) diff --git a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts index 1bb266a399..d3a5629cd5 100644 --- a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts @@ -1,25 +1,17 @@ import type { IndySdkSovDidCreateOptions } from '../src/dids/IndySdkSovDidRegistrar' import { Agent, TypedArrayEncoder, convertPublicKeyToX25519, JsonTransformer } from '@aries-framework/core' -import { getAgentOptions, genesisPath } from '@aries-framework/core/tests/helpers' import { generateKeyPairFromSeed } from '@stablelib/ed25519' +import { getAgentOptions } from '../../core/tests/helpers' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' -const agentOptions = getAgentOptions('Faber Dids Registrar', { - indyLedgers: [ - { - id: `localhost`, - isProduction: false, - genesisPath, - indyNamespace: 'localhost', - transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, - }, - ], -}) +import { indySdkModules } from './setupIndySdkModule' + +const agentOptions = getAgentOptions('Faber Dids Registrar', {}, indySdkModules) describe('dids', () => { - let agent: Agent + let agent: Agent beforeAll(async () => { agent = new Agent(agentOptions) @@ -59,6 +51,7 @@ describe('dids', () => { }, }) + console.log(did) expect(JsonTransformer.toJSON(did)).toMatchObject({ didDocumentMetadata: { qualifiedIndyDid: `did:indy:localhost:${indyDid}`, diff --git a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts index 3d85fa132e..dfa3281214 100644 --- a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -1,31 +1,12 @@ import type { IndySdkSovDidCreateOptions } from '../src/dids/IndySdkSovDidRegistrar' import { Agent, AriesFrameworkError, JsonTransformer } from '@aries-framework/core' -import { uuid } from '@aries-framework/core/src/utils/uuid' -import { genesisPath, getAgentOptions, taaAcceptanceMechanism, taaVersion } from '@aries-framework/core/tests/helpers' -import indySdk from 'indy-sdk' -import { IndySdkModule } from '../src' +import { getAgentOptions } from '../../core/tests/helpers' -const agent = new Agent( - getAgentOptions( - 'Indy SDK Sov DID resolver', - {}, - { - indySdk: new IndySdkModule({ - indySdk, - networks: [ - { - isProduction: false, - genesisPath, - indyNamespace: `pool:localtest:${uuid()}`, - transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, - }, - ], - }), - } - ) -) +import { getIndySdkModules } from './setupIndySdkModule' + +const agent = new Agent(getAgentOptions('Indy SDK Sov DID resolver', {}, getIndySdkModules())) describe('Indy SDK Sov DID resolver', () => { beforeAll(async () => { diff --git a/packages/indy-vdr/src/IndyVdrModule.ts b/packages/indy-vdr/src/IndyVdrModule.ts index 6150435c51..d03db3c08d 100644 --- a/packages/indy-vdr/src/IndyVdrModule.ts +++ b/packages/indy-vdr/src/IndyVdrModule.ts @@ -1,5 +1,5 @@ import type { IndyVdrModuleConfigOptions } from './IndyVdrModuleConfig' -import type { DependencyManager, Module } from '@aries-framework/core' +import type { AgentContext, DependencyManager, Module } from '@aries-framework/core' import { IndyVdrModuleConfig } from './IndyVdrModuleConfig' import { IndyVdrPoolService } from './pool/IndyVdrPoolService' @@ -21,4 +21,14 @@ export class IndyVdrModule implements Module { // Services dependencyManager.registerSingleton(IndyVdrPoolService) } + + public async initialize(agentContext: AgentContext): Promise { + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + + for (const pool of indyVdrPoolService.pools) { + if (pool.config.connectOnStartup) { + await pool.connect() + } + } + } } diff --git a/packages/indy-vdr/src/pool/IndyVdrPool.ts b/packages/indy-vdr/src/pool/IndyVdrPool.ts index 99ba0d1b06..08dcb03a4a 100644 --- a/packages/indy-vdr/src/pool/IndyVdrPool.ts +++ b/packages/indy-vdr/src/pool/IndyVdrPool.ts @@ -1,4 +1,4 @@ -import type { Logger, AgentContext, Key } from '@aries-framework/core' +import type { AgentContext, Key } from '@aries-framework/core' import type { IndyVdrRequest, IndyVdrPool as indyVdrPool } from '@hyperledger/indy-vdr-shared' import { TypedArrayEncoder } from '@aries-framework/core' @@ -35,16 +35,15 @@ export interface IndyVdrPoolConfig { isProduction: boolean indyNamespace: string transactionAuthorAgreement?: TransactionAuthorAgreement + connectOnStartup?: boolean } export class IndyVdrPool { private _pool?: indyVdrPool - private logger: Logger private poolConfig: IndyVdrPoolConfig public authorAgreement?: AuthorAgreement | null - public constructor(poolConfig: IndyVdrPoolConfig, logger: Logger) { - this.logger = logger + public constructor(poolConfig: IndyVdrPoolConfig) { this.poolConfig = poolConfig } diff --git a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts index 3fa6177465..661384e480 100644 --- a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts +++ b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts @@ -28,22 +28,7 @@ export class IndyVdrPoolService { this.logger = logger this.indyVdrModuleConfig = indyVdrModuleConfig - this.pools = this.indyVdrModuleConfig.networks.map((poolConfig) => new IndyVdrPool(poolConfig, this.logger)) - } - - /** - * Create connections to all ledger pools - */ - public async connectToPools() { - const handleArray: number[] = [] - // Sequentially connect to pools so we don't use up too many resources connecting in parallel - for (const pool of this.pools) { - this.logger.debug(`Connecting to pool: ${pool.indyNamespace}`) - const poolHandle = await pool.connect() - this.logger.debug(`Finished connection to pool: ${pool.indyNamespace}`) - handleArray.push(poolHandle) - } - return handleArray + this.pools = this.indyVdrModuleConfig.networks.map((poolConfig) => new IndyVdrPool(poolConfig)) } /** diff --git a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts index d45597ce8f..88701769bb 100644 --- a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts @@ -23,7 +23,6 @@ agent.dependencyManager.registerInstance(IndyVdrPoolService, indyVdrPoolService) describe('IndyVdrAnonCredsRegistry', () => { beforeAll(async () => { await agent.initialize() - await indyVdrPoolService.connectToPools() }) afterAll(async () => { diff --git a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts index e9f3aa4eab..564540c467 100644 --- a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts @@ -40,11 +40,7 @@ const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolSer describe('IndyVdrSov', () => { beforeAll(async () => { - await indyVdrPoolService.connectToPools() - - if (agentConfig.walletConfig) { - await wallet.createAndOpen(agentConfig.walletConfig) - } + await wallet.createAndOpen(agentConfig.walletConfig) signerKey = await wallet.createKey({ seed: '000000000000000000000000Trustee9', keyType: KeyType.Ed25519 }) }) diff --git a/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts index fea5b8fe0f..3afd7d86e2 100644 --- a/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts @@ -27,11 +27,7 @@ let signerKey: Key describe('IndyVdrPoolService', () => { beforeAll(async () => { - await indyVdrPoolService.connectToPools() - - if (agentConfig.walletConfig) { - await wallet.createAndOpen(agentConfig.walletConfig) - } + await wallet.createAndOpen(agentConfig.walletConfig) signerKey = await wallet.createKey({ seed: '000000000000000000000000Trustee9', keyType: KeyType.Ed25519 }) }) diff --git a/yarn.lock b/yarn.lock index e63f20b24a..44caceb35e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2331,7 +2331,7 @@ dependencies: "@stablelib/int" "^1.0.1" -"@stablelib/ed25519@^1.0.2": +"@stablelib/ed25519@^1.0.2", "@stablelib/ed25519@^1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@stablelib/ed25519/-/ed25519-1.0.3.tgz#f8fdeb6f77114897c887bb6a3138d659d3f35996" integrity sha512-puIMWaX9QlRsbhxfDc5i+mNPMY+0TmQEskunY1rZEBPi1acBCVQAhnsk/1Hk50DGPtVsZtAWQg4NHGlVaO9Hqg== @@ -2585,6 +2585,11 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/libsodium-wrappers@^0.7.5": + version "0.7.10" + resolved "https://registry.yarnpkg.com/@types/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz#a6ebde70d3b4af960fd802af8d0e3c7cfe281eb2" + integrity sha512-BqI9B92u+cM3ccp8mpHf+HzJ8fBlRwdmyd6+fz3p99m3V6ifT5O3zmOMi612PGkpeFeG/G6loxUnzlDNhfjPSA== + "@types/luxon@^1.27.0": version "1.27.1" resolved "https://registry.yarnpkg.com/@types/luxon/-/luxon-1.27.1.tgz#aceeb2d5be8fccf541237e184e37ecff5faa9096" @@ -3475,6 +3480,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +base-58@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/base-58/-/base-58-0.0.1.tgz#85d3e70251075661933388f831d1eb8b8f6314e3" + integrity sha512-denlKTnozZTVWuh1QkbXf10kkFNc+0/eno29RR+6g5al0yGI+iAOFt/cIA2tvnKoADlUFLZHs50ZdWF+c9WBnw== + base-64@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb" @@ -3534,7 +3544,7 @@ bindings@^1.3.1: dependencies: file-uri-to-path "1.0.0" -bn.js@^5.2.0, bn.js@^5.2.1: +bn.js@^5.2.1: version "5.2.1" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-5.2.1.tgz#0bc527a6a0d18d0aa8d5b0538ce4a77dccfa7b70" integrity sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ== @@ -3637,7 +3647,7 @@ bs-logger@0.x: dependencies: fast-json-stable-stringify "2.x" -bs58@4.0.1: +bs58@4.0.1, bs58@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/bs58/-/bs58-4.0.1.tgz#be161e76c354f6f788ae4071f63f34e8c4f0a42a" integrity sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw== @@ -4750,6 +4760,17 @@ encoding@^0.1.12: dependencies: iconv-lite "^0.6.2" +encryption-envelope-js@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/encryption-envelope-js/-/encryption-envelope-js-1.2.5.tgz#fe9399f893a634e90aeb4e59d108a801fe865f8e" + integrity sha512-QjIuFou3vZTqRhn24rOvLHeKd8Q6ZBkd8crnQDWkJPNCsKAVlv4bMmHDOIhVD/Px4TFFJZsnTEvPRH1QdXOXTg== + dependencies: + "@types/libsodium-wrappers" "^0.7.5" + base-58 "0.0.1" + bs58 "^4.0.1" + libsodium "^0.7.3" + libsodium-wrappers "^0.7.3" + end-of-stream@^1.1.0: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -7502,6 +7523,18 @@ libphonenumber-js@^1.10.14, libphonenumber-js@^1.9.7: resolved "https://registry.yarnpkg.com/libphonenumber-js/-/libphonenumber-js-1.10.18.tgz#657c419071c8a02c638c0e80d9ee1232f152f280" integrity sha512-NS4ZEgNhwbcPz1gfSXCGFnQm0xEiyTSPRthIuWytDzOiEG9xnZ2FbLyfJC4tI2BMAAXpoWbNxHYH75pa3Dq9og== +libsodium-wrappers@^0.7.10, libsodium-wrappers@^0.7.3: + version "0.7.10" + resolved "https://registry.yarnpkg.com/libsodium-wrappers/-/libsodium-wrappers-0.7.10.tgz#13ced44cacb0fc44d6ac9ce67d725956089ce733" + integrity sha512-pO3F1Q9NPLB/MWIhehim42b/Fwb30JNScCNh8TcQ/kIc+qGLQch8ag8wb0keK3EP5kbGakk1H8Wwo7v+36rNQg== + dependencies: + libsodium "^0.7.0" + +libsodium@^0.7.0, libsodium@^0.7.3: + version "0.7.10" + resolved "https://registry.yarnpkg.com/libsodium/-/libsodium-0.7.10.tgz#c2429a7e4c0836f879d701fec2c8a208af024159" + integrity sha512-eY+z7hDrDKxkAK+QKZVNv92A5KYkxfvIshtBJkmg5TSiCnYqZP3i9OO9whE79Pwgm4jGaoHgkM4ao/b9Cyu4zQ== + lines-and-columns@^1.1.6: version "1.2.4" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632" From d24d426294146564d7e595b596009e6934cf8562 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 14 Feb 2023 12:07:03 +0100 Subject: [PATCH 08/22] temp Signed-off-by: Timo Glastra --- .../anoncreds/tests/legacyAnonCredsSetup.ts | 52 ++++++++------ .../SingleContextLruCacheRecord.ts | 3 + .../v2-indy-proof-negotiation.test.ts | 15 ++-- .../MediationRecipientService.test.ts | 11 --- .../vc/__tests__/W3cCredentialService.test.ts | 14 ++-- packages/core/tests/helpers.ts | 10 +-- .../core/tests/proofs-sub-protocol.test.ts | 71 ++++++++++++++++--- .../services/IndySdkAnonCredsRegistry.ts | 3 +- packages/indy-sdk/src/ledger/IndySdkPool.ts | 11 ++- 9 files changed, 125 insertions(+), 65 deletions(-) diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index b601dfce68..07efc5ffe8 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -17,6 +17,8 @@ import type { } from '@aries-framework/core' import { + CacheModule, + InMemoryLruCache, Agent, AriesFrameworkError, AutoAcceptCredential, @@ -30,7 +32,6 @@ import { V2ProofProtocol, DidsModule, } from '@aries-framework/core' -import testLogger from '@aries-framework/core/tests/logger' import { randomUUID } from 'crypto' import indySdk from 'indy-sdk' import { ReplaySubject, Subject } from 'rxjs' @@ -46,6 +47,7 @@ import { waitForCredentialRecordSubject, waitForProofExchangeRecordSubject, } from '../../core/tests/helpers' +import testLogger from '../../core/tests/logger' import { IndySdkAnonCredsRegistry, IndySdkModule, @@ -103,11 +105,15 @@ export const getLegacyAnonCredsModules = ({ { isProduction: false, genesisPath, + id: randomUUID(), indyNamespace: `pool:localtest`, transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, }, ], }), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), } as const return modules @@ -248,10 +254,10 @@ export async function issueLegacyAnonCredsCredential({ interface SetupAnonCredsTestReturn { issuerAgent: AnonCredsTestsAgent - issuerReplay: ReplaySubject + issuerReplay: ReplaySubject holderAgent: AnonCredsTestsAgent - holderReplay: ReplaySubject + holderReplay: ReplaySubject issuerHolderConnectionId: string holderIssuerConnectionId: string @@ -260,9 +266,7 @@ interface SetupAnonCredsTestReturn { holderVerifierConnectionId: VerifierName extends string ? string : undefined verifierAgent: VerifierName extends string ? AnonCredsTestsAgent : undefined - verifierReplay: VerifierName extends string - ? ReplaySubject - : undefined + verifierReplay: VerifierName extends string ? ReplaySubject : undefined credentialDefinitionId: string } @@ -353,12 +357,9 @@ export async function setupAnonCredsTests() - const holderReplay = new ReplaySubject() - - const verifierReplay = verifierAgent - ? new ReplaySubject() - : undefined + const issuerReplay = new ReplaySubject() + const holderReplay = new ReplaySubject() + const verifierReplay = verifierAgent ? new ReplaySubject() : undefined issuerAgent.events .observable(CredentialEventTypes.CredentialStateChanged) @@ -425,17 +426,19 @@ async function registerSchema( agent: AnonCredsTestsAgent, schema: AnonCredsSchema ): Promise { - const { schemaState, registrationMetadata } = await agent.modules.anoncreds.registerSchema({ + const { schemaState } = await agent.modules.anoncreds.registerSchema({ schema, options: { - didIndyNamespace: 'local:test', + didIndyNamespace: 'pool:localtest', }, }) testLogger.test(`created schema with id ${schemaState.schemaId}`, schema) if (schemaState.state !== 'finished') { - throw new AriesFrameworkError(`Schema not created: ${registrationMetadata.error}`) + throw new AriesFrameworkError( + `Schema not created: ${schemaState.state === 'failed' ? schemaState.reason : 'Not finished'}` + ) } return schemaState @@ -445,16 +448,19 @@ async function registerCredentialDefinition( agent: AnonCredsTestsAgent, credentialDefinition: AnonCredsRegisterCredentialDefinitionOptions ): Promise { - const { registrationMetadata, credentialDefinitionState } = - await agent.modules.anoncreds.registerCredentialDefinition({ - credentialDefinition, - options: { - didIndyNamespace: 'local:test', - }, - }) + const { credentialDefinitionState } = await agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition, + options: { + didIndyNamespace: 'pool:localtest', + }, + }) if (credentialDefinitionState.state !== 'finished') { - throw new AriesFrameworkError(`Credential definition not created: ${registrationMetadata.error}`) + throw new AriesFrameworkError( + `Credential definition not created: ${ + credentialDefinitionState.state === 'failed' ? credentialDefinitionState.reason : 'Not finished' + }` + ) } return credentialDefinitionState diff --git a/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRecord.ts b/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRecord.ts index 0016ed3d8e..257b6b6080 100644 --- a/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRecord.ts +++ b/packages/core/src/modules/cache/singleContextLruCache/SingleContextLruCacheRecord.ts @@ -1,5 +1,7 @@ import type { TagsBase } from '../../../storage/BaseRecord' +import { Type } from 'class-transformer' + import { BaseRecord } from '../../../storage/BaseRecord' import { uuid } from '../../../utils/uuid' @@ -17,6 +19,7 @@ export interface SingleContextLruCacheProps { } export class SingleContextLruCacheRecord extends BaseRecord { + @Type(() => Object) public entries!: Map public static readonly type = 'SingleContextLruCacheRecord' diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts index f35b4da5d3..72c1ee44ef 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts @@ -5,6 +5,8 @@ import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecor import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' import type { CredDefId } from 'indy-sdk' +import { AnonCredsProofRequest } from '../../../../../../../anoncreds/src/models/AnonCredsProofRequest' +import { setupAnonCredsTests } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage/didcomm' @@ -29,10 +31,11 @@ describe('Present Proof', () => { beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) + ;({ faberAgent, aliceAgent, credDefId, aliceConnection, presentationPreview } = await setupAnonCredsTests({ + issuerName: 'Faber agent', + holderName: 'Alice agent', + attributeNames: ['name', 'image_0'], + })) }) afterAll(async () => { @@ -407,11 +410,11 @@ describe('Present Proof', () => { const proofRequest = JsonTransformer.fromJSON( proofRequestMessage.requestAttachments[0].getDataAsJson(), - ProofRequest + AnonCredsProofRequest ) const predicateKey = proofRequest.requestedPredicates?.keys().next().value - expect(proofRequest.toJSON()).toMatchObject({ + expect(JsonTransformer.toJSON(proofRequest)).toMatchObject({ name: 'proof-request', nonce: expect.any(String), version: '1.0', diff --git a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts index 25dd20538b..e00b530afc 100644 --- a/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts +++ b/packages/core/src/modules/routing/services/__tests__/MediationRecipientService.test.ts @@ -1,5 +1,4 @@ import type { AgentContext } from '../../../../agent' -import type { Wallet } from '../../../../wallet/Wallet' import type { Routing } from '../../../connections/services/ConnectionService' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../tests/helpers' @@ -8,11 +7,9 @@ import { AgentEventTypes } from '../../../../agent/Events' import { MessageSender } from '../../../../agent/MessageSender' import { InboundMessageContext } from '../../../../agent/models/InboundMessageContext' import { Key } from '../../../../crypto' -import { SigningProviderRegistry } from '../../../../crypto/signing-provider' import { Attachment } from '../../../../decorators/attachment/Attachment' import { AriesFrameworkError } from '../../../../error' import { uuid } from '../../../../utils/uuid' -import { IndyWallet } from '../../../../wallet/IndyWallet' import { DidExchangeState } from '../../../connections' import { ConnectionMetadataKeys } from '../../../connections/repository/ConnectionMetadataTypes' import { ConnectionRepository } from '../../../connections/repository/ConnectionRepository' @@ -63,7 +60,6 @@ describe('MediationRecipientService', () => { connectionImageUrl, }) - let wallet: Wallet let mediationRepository: MediationRepository let didRepository: DidRepository let didRegistrarService: DidRegistrarService @@ -76,16 +72,9 @@ describe('MediationRecipientService', () => { let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) agentContext = getAgentContext({ agentConfig: config, }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) - }) - - afterAll(async () => { - await wallet.delete() }) beforeEach(async () => { diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index d6f3eb266f..04d4b644bd 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -1,10 +1,13 @@ import type { AgentContext } from '../../../agent' +import type { Wallet } from '../../../wallet' +import indySdk from 'indy-sdk' + +import { IndySdkWallet } from '../../../../../indy-sdk/src' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { KeyType } from '../../../crypto' import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { JsonTransformer } from '../../../utils/JsonTransformer' -import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' import { DidKey } from '../../dids' import { @@ -42,8 +45,6 @@ const signatureSuiteRegistry = new SignatureSuiteRegistry([ const signingProviderRegistry = new SigningProviderRegistry([]) -jest.mock('../../ledger/services/IndyLedgerService') - jest.mock('../repository/W3cCredentialRepository') const W3cCredentialRepositoryMock = W3cCredentialRepository as jest.Mock @@ -63,16 +64,15 @@ const credentialRecordFactory = async (credential: W3cVerifiableCredential) => { } describe('W3cCredentialService', () => { - let wallet: IndyWallet + let wallet: Wallet let agentContext: AgentContext let w3cCredentialService: W3cCredentialService let w3cCredentialRepository: W3cCredentialRepository const seed = 'testseed000000000000000000000001' beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, signingProviderRegistry) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) + wallet = new IndySdkWallet(indySdk, agentConfig.logger, signingProviderRegistry) + await wallet.createAndOpen(agentConfig.walletConfig) agentContext = getAgentContext({ agentConfig, wallet, diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index b5a07fee04..4c268d07ed 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -1,5 +1,4 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { Observable } from 'rxjs' import type { AgentDependencies, BaseEvent, @@ -11,10 +10,14 @@ import type { InjectionToken, ProofStateChangedEvent, Wallet, + Agent, + CredentialState, } from '../src' import type { AgentModulesInput, EmptyModuleMap } from '../src/agent/AgentModules' import type { TrustPingReceivedEvent, TrustPingResponseReceivedEvent } from '../src/modules/connections/TrustPingEvents' +import type { ProofState } from '../src/modules/proofs/models/ProofState' import type { WalletConfig } from '../src/types' +import type { Observable } from 'rxjs' import { readFileSync } from 'fs' import path from 'path' @@ -23,13 +26,11 @@ import { catchError, filter, map, timeout } from 'rxjs/operators' import { agentDependencies, WalletScheme } from '../../node/src' import { - Agent, AgentConfig, AgentContext, BasicMessageEventTypes, ConnectionRecord, CredentialEventTypes, - CredentialState, DependencyManager, DidExchangeRole, DidExchangeState, @@ -45,7 +46,6 @@ import { OutOfBandRole } from '../src/modules/oob/domain/OutOfBandRole' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' import { OutOfBandInvitation } from '../src/modules/oob/messages' import { OutOfBandRecord } from '../src/modules/oob/repository' -import { ProofState } from '../src/modules/proofs/models/ProofState' import { KeyDerivationMethod } from '../src/types' import testLogger, { TestLogger } from './logger' @@ -61,6 +61,8 @@ export const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${numb export const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' export { agentDependencies } +export type EventReplaySubject = ReplaySubject + export function getAgentOptions( name: string, extraConfig: Partial = {}, diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index 03be108090..fa69bd7de6 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -1,8 +1,7 @@ -import type { V1PresentationPreview } from '../../anoncreds/src' +import type { EventReplaySubject } from './helpers' import type { AnonCredsTestsAgent } from '../../anoncreds/tests/legacyAnonCredsSetup' -import type { ProofExchangeRecord } from '../src' -import { setupAnonCredsTests } from '../../anoncreds/tests/legacyAnonCredsSetup' +import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../anoncreds/tests/legacyAnonCredsSetup' import { ProofState } from '../src/modules/proofs/models/ProofState' import { uuid } from '../src/utils/uuid' @@ -11,25 +10,49 @@ import testLogger from './logger' describe('Present Proof Subprotocol', () => { let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject let credentialDefinitionId: string let faberConnectionId: string let aliceConnectionId: string - let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: V1PresentationPreview beforeAll(async () => { testLogger.test('Initializing the agents') ;({ issuerAgent: faberAgent, + issuerReplay: faberReplay, holderAgent: aliceAgent, + holderReplay: aliceReplay, credentialDefinitionId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, } = await setupAnonCredsTests({ issuerName: 'Faber agent', holderName: 'Alice agent', + attributeNames: ['name', 'age'], })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '50', + }, + ], + credentialDefinitionId, + }, + }) }) afterAll(async () => { @@ -51,7 +74,7 @@ describe('Present Proof Subprotocol', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnectionId, protocolVersion: 'v1', parentThreadId, @@ -59,8 +82,21 @@ describe('Present Proof Subprotocol', () => { indy: { name: 'abc', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + credentialDefinitionId, + value: 'Alice', + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 40, + }, + ], }, }, }) @@ -214,7 +250,7 @@ describe('Present Proof Subprotocol', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ connectionId: aliceConnectionId, protocolVersion: 'v2', parentThreadId, @@ -222,8 +258,21 @@ describe('Present Proof Subprotocol', () => { indy: { name: 'abc', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + credentialDefinitionId, + value: 'Alice', + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 40, + }, + ], }, }, }) diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index 5c25805cbd..f53b30b3e8 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -305,7 +305,8 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const request = await indySdk.buildCredDefRequest(options.credentialDefinition.issuerId, { id: credentialDefinitionId, - schemaId: options.credentialDefinition.schemaId, + // Indy ledger requires the credential schemaId to be a string of the schema seqNo. + schemaId: schemaMetadata.indyLedgerSeqNo.toString(), tag: options.credentialDefinition.tag, type: options.credentialDefinition.type, value: options.credentialDefinition.value, diff --git a/packages/indy-sdk/src/ledger/IndySdkPool.ts b/packages/indy-sdk/src/ledger/IndySdkPool.ts index d7720ad44e..b5f6f328d3 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPool.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPool.ts @@ -3,7 +3,7 @@ import type { FileSystem, Logger } from '@aries-framework/core' import type { LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' import type { Subject } from 'rxjs' -import { AriesFrameworkError } from '@aries-framework/core' +import { AriesFrameworkError, utils } from '@aries-framework/core' import { isIndyError, IndySdkError } from '../error' @@ -16,8 +16,15 @@ export interface TransactionAuthorAgreement { } export interface IndySdkPoolConfig { + /** + * Optional id that influences the pool config that is created by the indy-sdk. + * Uses the indyNamespace as the pool identifier if not provided. + */ + id?: string + genesisPath?: string genesisTransactions?: string + isProduction: boolean indyNamespace: string transactionAuthorAgreement?: TransactionAuthorAgreement @@ -99,7 +106,7 @@ export class IndySdkPool { } private async connectToLedger() { - const poolName = this.poolConfig.indyNamespace + const poolName = `${this.poolConfig.indyNamespace}-${utils.uuid()}` const genesisPath = await this.getGenesisPath() if (!genesisPath) { From 4c0a27bc216aaabde3bb7d993b24d21eefc0dc4c Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 14 Feb 2023 17:26:11 +0100 Subject: [PATCH 09/22] temp Signed-off-by: Timo Glastra --- demo/package.json | 5 + demo/src/BaseAgent.ts | 54 +- .../LegacyIndyCredentialFormatService.ts | 4 +- .../src/models/AnonCredsProofRequest.ts | 7 +- .../v1-credentials-auto-accept.e2e.test.ts | 16 +- .../anoncreds/tests/legacyAnonCredsSetup.ts | 148 ++-- packages/askar/tests/helpers.ts | 9 - .../signature/SignatureDecoratorUtils.test.ts | 10 +- .../__tests__/ConnectionService.test.ts | 8 +- .../__tests__/CredentialsModule.test.ts | 7 +- .../RevocationNotificationService.test.ts | 4 +- .../V2CredentialProtocolCred.test.ts | 138 ++-- .../V2CredentialProtocolOffer.test.ts | 115 ++-- .../v2-connectionless-credentials.e2e.test.ts | 42 +- .../v2-credentials-auto-accept.e2e.test.ts | 116 ++-- .../v2/__tests__/v2-credentials.e2e.test.ts | 123 ++-- ...ldproof.connectionless-credentials.test.ts | 175 ++--- ...v2.ldproof.credentials-auto-accept.test.ts | 147 ++-- ...f.credentials.propose-offerED25519.test.ts | 642 ++++++++++-------- .../dids/__tests__/DidsModuleConfig.test.ts | 17 +- .../modules/dids/__tests__/peer-did.test.ts | 15 +- .../core/src/modules/proofs/ProofsModule.ts | 4 +- .../v2/__tests__/V2ProofProtocol.test.ts | 7 +- .../v2-indy-connectionless-proofs.e2e.test.ts | 374 +++++----- .../v2-indy-proof-negotiation.test.ts | 288 ++++---- .../v2-indy-proof-presentation.e2e.test.ts | 157 +++-- .../v2-indy-proof-proposal.e2e.test.ts | 94 --- .../v2-indy-proof-request.e2e.test.ts | 132 ++-- .../v2-indy-proofs-auto-accept.2e.test.ts | 248 ++++--- .../v2/__tests__/v2-indy-proofs.e2e.test.ts | 8 +- packages/core/tests/events.ts | 21 + packages/core/tests/helpers.ts | 2 - packages/core/tests/index.ts | 8 + packages/core/tests/jsonld.ts | 162 +++++ .../core/tests/proofs-sub-protocol.test.ts | 2 +- packages/core/tests/transport.ts | 18 + 36 files changed, 1744 insertions(+), 1583 deletions(-) delete mode 100644 packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-proposal.e2e.test.ts create mode 100644 packages/core/tests/events.ts create mode 100644 packages/core/tests/index.ts create mode 100644 packages/core/tests/jsonld.ts create mode 100644 packages/core/tests/transport.ts diff --git a/demo/package.json b/demo/package.json index b41cae2066..b4200b311f 100644 --- a/demo/package.json +++ b/demo/package.json @@ -14,7 +14,12 @@ "refresh": "rm -rf ./node_modules ./yarn.lock && yarn" }, "devDependencies": { + "@aries-framework/anoncreds": "*", + "@aries-framework/anoncreds-rs": "*", + "@aries-framework/askar": "*", "@aries-framework/core": "*", + "@aries-framework/indy-sdk": "*", + "@aries-framework/indy-vdr": "*", "@aries-framework/node": "*", "@types/figlet": "^1.5.4", "@types/inquirer": "^8.1.3", diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index abf507014e..1f87aa7942 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -1,6 +1,20 @@ +import type { IndySdkPoolConfig } from '../../packages/indy-sdk/src/ledger' +import type { IndyVdrPoolConfig } from '../../packages/indy-vdr/src/pool' import type { InitConfig } from '@aries-framework/core' -import { Agent, AutoAcceptCredential, AutoAcceptProof, HttpOutboundTransport } from '@aries-framework/core' +import { AnonCredsModule } from '@aries-framework/anoncreds' +import { AnonCredsRsModule } from '@aries-framework/anoncreds-rs' +import { AskarModule } from '@aries-framework/askar' +import { + ProofsModule, + AutoAcceptProof, + AutoAcceptCredential, + CredentialsModule, + Agent, + HttpOutboundTransport, +} from '@aries-framework/core' +import { IndySdkAnonCredsRegistry, IndySdkSovDidResolver, IndySdkSovDidRegistrar } from '@aries-framework/indy-sdk' +import { IndyVdrSovDidResolver } from '@aries-framework/indy-vdr' import { agentDependencies, HttpInboundTransport } from '@aries-framework/node' import { greenText } from './OutputClass' @@ -10,6 +24,12 @@ const bcovrin = `{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blsk {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node3","blskey":"3WFpdbg7C5cnLYZwFZevJqhubkFALBfCBBok15GdrKMUhUjGsk3jV6QKj6MZgEubF7oqCafxNdkm7eswgA4sdKTRc82tLGzZBd6vNqU8dupzup6uYUf32KTHTPQbuUM8Yk4QFXjEf2Usu2TJcNkdgpyeUSX42u5LqdDDpNSWUK5deC5","blskey_pop":"QwDeb2CkNSx6r8QC8vGQK3GRv7Yndn84TGNijX8YXHPiagXajyfTjoR87rXUu4G4QLk2cF8NNyqWiYMus1623dELWwx57rLCFqGh7N4ZRbGDRP4fnVcaKg1BcUxQ866Ven4gw8y4N56S5HzxXNBZtLYmhGHvDtk6PFkFwCvxYrNYjh","client_ip":"138.197.138.255","client_port":9706,"node_ip":"138.197.138.255","node_port":9705,"services":["VALIDATOR"]},"dest":"DKVxG2fXXTU8yT5N7hGEbXB3dfdAnYv1JczDUHpmDxya"},"metadata":{"from":"4cU41vWW82ArfxJxHkzXPG"},"type":"0"},"txnMetadata":{"seqNo":3,"txnId":"7e9f355dffa78ed24668f0e0e369fd8c224076571c51e2ea8be5f26479edebe4"},"ver":"1"} {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"138.197.138.255","client_port":9708,"node_ip":"138.197.138.255","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"}` +const indyNetworkConfig = { + genesisTransactions: bcovrin, + indyNamespace: 'bcovrin:test', + isProduction: false, +} satisfies IndySdkPoolConfig | IndyVdrPoolConfig + export class BaseAgent { public port: number public name: string @@ -20,30 +40,36 @@ export class BaseAgent { this.name = name this.port = port - const config: InitConfig = { + const config = { label: name, walletConfig: { id: name, key: name, }, publicDidSeed: '6b8b882e2618fa5d45ee7229ca880083', - indyLedgers: [ - { - genesisTransactions: bcovrin, - id: 'greenlights' + name, - indyNamespace: 'greenlights' + name, - isProduction: false, - }, - ], endpoints: [`http://localhost:${this.port}`], autoAcceptConnections: true, - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - autoAcceptProofs: AutoAcceptProof.ContentApproved, - } + } satisfies InitConfig this.config = config - this.agent = new Agent({ config, dependencies: agentDependencies }) + this.agent = new Agent({ + config, + dependencies: agentDependencies, + modules: { + credentials: new CredentialsModule({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }), + proofs: new ProofsModule({ + autoAcceptProofs: AutoAcceptProof.ContentApproved, + }), + anoncreds: new AnonCredsModule({ + registries: [new IndySdkAnonCredsRegistry()], + }), + anoncredsRs: new AnonCredsRsModule(), + indyVdr: new IndyVdrModule(), + }, + }) this.agent.registerInboundTransport(new HttpInboundTransport({ port })) this.agent.registerOutboundTransport(new HttpOutboundTransport()) } diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts index dd50a79e44..9fdbb38cf8 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -7,7 +7,7 @@ import type { } from '../models' import type { AnonCredsIssuerService, AnonCredsHolderService, GetRevocationRegistryDefinitionReturn } from '../services' import type { AnonCredsCredentialMetadata } from '../utils/metadata' -import { +import type { CredentialFormatService, AgentContext, CredentialFormatCreateProposalOptions, @@ -27,10 +27,10 @@ import { CredentialExchangeRecord, CredentialPreviewAttributeOptions, LinkedAttachment, - ProblemReportError, } from '@aries-framework/core' import { + ProblemReportError, MessageValidator, CredentialFormatSpec, AriesFrameworkError, diff --git a/packages/anoncreds/src/models/AnonCredsProofRequest.ts b/packages/anoncreds/src/models/AnonCredsProofRequest.ts index 34abfe3030..6c7b7aca5e 100644 --- a/packages/anoncreds/src/models/AnonCredsProofRequest.ts +++ b/packages/anoncreds/src/models/AnonCredsProofRequest.ts @@ -1,6 +1,5 @@ import type { AnonCredsRequestedPredicateOptions } from './AnonCredsRequestedPredicate' -import { IndyRevocationInterval } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsIn, IsInstance, IsOptional, IsString, ValidateNested } from 'class-validator' @@ -72,10 +71,10 @@ export class AnonCredsProofRequest { @Expose({ name: 'non_revoked' }) @ValidateNested() - @Type(() => IndyRevocationInterval) + @Type(() => AnonCredsRevocationInterval) @IsOptional() - @IsInstance(IndyRevocationInterval) - public nonRevoked?: IndyRevocationInterval + @IsInstance(AnonCredsRevocationInterval) + public nonRevoked?: AnonCredsRevocationInterval @IsIn(['1.0', '2.0']) @IsOptional() diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index 79670842a2..fec47bc19f 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -35,7 +35,7 @@ describe('V1 Credentials Auto Accept', () => { let faberConnectionId: string let aliceConnectionId: string - describe('Auto accept on `always`', () => { + describe("Auto accept on 'always'", () => { beforeAll(async () => { ;({ issuerAgent: faberAgent, @@ -57,7 +57,7 @@ describe('V1 Credentials Auto Accept', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + test("Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ @@ -100,7 +100,7 @@ describe('V1 Credentials Auto Accept', () => { }) }) - test('Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + test("Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Faber sends credential offer to Alice') const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', @@ -153,7 +153,7 @@ describe('V1 Credentials Auto Accept', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + describe("Auto accept on 'contentApproved'", () => { beforeAll(async () => { ;({ issuerAgent: faberAgent, @@ -180,7 +180,7 @@ describe('V1 Credentials Auto Accept', () => { // ============================== // TESTS v1 BEGIN // ========================== - test('Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + test("Alice starts with V1 credential proposal to Faber, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Alice sends credential proposal to Faber') let aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnectionId, @@ -261,7 +261,7 @@ describe('V1 Credentials Auto Accept', () => { }) }) - test('Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + test("Faber starts with V1 credential offer to Alice, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', @@ -346,7 +346,7 @@ describe('V1 Credentials Auto Accept', () => { } }) - test('Faber starts with V1 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Faber starts with V1 credential offer to Alice, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', @@ -401,7 +401,7 @@ describe('V1 Credentials Auto Accept', () => { aliceCredentialExchangeRecord.assertState(CredentialState.ProposalSent) }) - test('Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Alice starts with V1 credential proposal to Faber, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnectionId, diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 07efc5ffe8..61a0442771 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -1,4 +1,4 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { EventReplaySubject } from '../../core/tests' import type { AnonCredsRegisterCredentialDefinitionOptions, AnonCredsRequestedAttribute, @@ -8,13 +8,7 @@ import type { RegisterCredentialDefinitionReturnStateFinished, RegisterSchemaReturnStateFinished, } from '../src' -import type { - AutoAcceptProof, - BaseEvent, - ConnectionRecord, - CredentialStateChangedEvent, - ProofStateChangedEvent, -} from '@aries-framework/core' +import type { AutoAcceptProof, ConnectionRecord } from '@aries-framework/core' import { CacheModule, @@ -34,10 +28,8 @@ import { } from '@aries-framework/core' import { randomUUID } from 'crypto' import indySdk from 'indy-sdk' -import { ReplaySubject, Subject } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { setupSubjectTransports, setupEventReplaySubjects } from '../../core/tests' import { genesisPath, getAgentOptions, @@ -131,10 +123,10 @@ export async function presentLegacyAnonCredsProof({ request: { attributes, predicates }, }: { holderAgent: AnonCredsTestsAgent - holderReplay: ReplaySubject + holderReplay: EventReplaySubject verifierAgent: AnonCredsTestsAgent - verifierReplay: ReplaySubject + verifierReplay: EventReplaySubject verifierHolderConnectionId: string request: { @@ -207,10 +199,10 @@ export async function issueLegacyAnonCredsCredential({ offer, }: { issuerAgent: AnonCredsTestsAgent - issuerReplay: Subject + issuerReplay: EventReplaySubject holderAgent: AnonCredsTestsAgent - holderReplay: Subject + holderReplay: EventReplaySubject issuerHolderConnectionId: string offer: AnonCredsOfferCredentialFormat @@ -252,32 +244,44 @@ export async function issueLegacyAnonCredsCredential({ } } -interface SetupAnonCredsTestReturn { +interface SetupAnonCredsTestsReturn { issuerAgent: AnonCredsTestsAgent - issuerReplay: ReplaySubject + issuerReplay: EventReplaySubject holderAgent: AnonCredsTestsAgent - holderReplay: ReplaySubject - - issuerHolderConnectionId: string - holderIssuerConnectionId: string - - verifierHolderConnectionId: VerifierName extends string ? string : undefined - holderVerifierConnectionId: VerifierName extends string ? string : undefined + holderReplay: EventReplaySubject + + issuerHolderConnectionId: CreateConnections extends true ? string : undefined + holderIssuerConnectionId: CreateConnections extends true ? string : undefined + + verifierHolderConnectionId: CreateConnections extends true + ? VerifierName extends string + ? string + : undefined + : undefined + holderVerifierConnectionId: CreateConnections extends true + ? VerifierName extends string + ? string + : undefined + : undefined verifierAgent: VerifierName extends string ? AnonCredsTestsAgent : undefined - verifierReplay: VerifierName extends string ? ReplaySubject : undefined + verifierReplay: VerifierName extends string ? EventReplaySubject : undefined credentialDefinitionId: string } -export async function setupAnonCredsTests({ +export async function setupAnonCredsTests< + VerifierName extends string | undefined = undefined, + CreateConnections extends boolean = true +>({ issuerName, holderName, verifierName, autoAcceptCredentials, autoAcceptProofs, attributeNames, + createConnections, }: { issuerName: string holderName: string @@ -285,16 +289,8 @@ export async function setupAnonCredsTests> { - const issuerMessages = new Subject() - const holderMessages = new Subject() - const verifierMessages = new Subject() - const subjectMap = { - 'rxjs:issuer': issuerMessages, - 'rxjs:holder': holderMessages, - 'rxjs:verifier': verifierMessages, - } - + createConnections?: CreateConnections +}): Promise> { const modules = getLegacyAnonCredsModules({ autoAcceptCredentials, autoAcceptProofs, @@ -309,9 +305,6 @@ export async function setupAnonCredsTests() - const holderReplay = new ReplaySubject() - const verifierReplay = verifierAgent ? new ReplaySubject() : undefined - - issuerAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(issuerReplay) - issuerAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(issuerReplay) - - holderAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(holderReplay) - holderAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(holderReplay) - - if (verifierAgent) { - verifierAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(verifierReplay) - verifierAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(verifierReplay) + if (createConnections ?? true) { + ;[issuerHolderConnection, holderIssuerConnection] = await makeConnection(issuerAgent, holderAgent) + + if (verifierName) { + ;[holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) + } } return { @@ -385,17 +363,17 @@ export async function setupAnonCredsTests + } as unknown as SetupAnonCredsTestsReturn } export async function prepareForAnonCredsIssuance( diff --git a/packages/askar/tests/helpers.ts b/packages/askar/tests/helpers.ts index 17a521a1af..d6e9ba0727 100644 --- a/packages/askar/tests/helpers.ts +++ b/packages/askar/tests/helpers.ts @@ -26,18 +26,9 @@ export function getPostgresAgentOptions( key: `Key${name}`, storage: storageConfig, }, - connectToIndyLedgersOnStartup: false, publicDidSeed, autoAcceptConnections: true, autoUpdateStorageOnStartup: false, - indyLedgers: [ - { - id: `pool-${name}`, - indyNamespace: `pool:localtest`, - isProduction: false, - genesisPath, - }, - ], logger: new TestLogger(LogLevel.off, name), ...extraConfig, } diff --git a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts index 894520edaf..4fd501ee3a 100644 --- a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts +++ b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts @@ -1,7 +1,11 @@ +import type { Wallet } from '../../wallet' + +import indySdk from 'indy-sdk' + +import { IndySdkWallet } from '../../../../indy-sdk/src' import { getAgentConfig } from '../../../tests/helpers' import { KeyType } from '../../crypto' import { SigningProviderRegistry } from '../../crypto/signing-provider' -import { IndyWallet } from '../../wallet/IndyWallet' import { SignatureDecorator } from './SignatureDecorator' import { signData, unpackAndVerifySignatureDecorator } from './SignatureDecoratorUtils' @@ -39,11 +43,11 @@ describe('Decorators | Signature | SignatureDecoratorUtils', () => { signer: 'GjZWsBLgZCR18aL468JAT7w9CZRiBnpxUPPgyQxh4voa', }) - let wallet: IndyWallet + let wallet: Wallet beforeAll(async () => { const config = getAgentConfig('SignatureDecoratorUtilsTest') - wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) + wallet = new IndySdkWallet(indySdk, config.logger, new SigningProviderRegistry([])) // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(config.walletConfig!) }) diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 9cc403ecba..534dcd97e2 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -3,8 +3,10 @@ import type { Wallet } from '../../../wallet/Wallet' import type { DidDocument } from '../../dids' import type { Routing } from '../services/ConnectionService' +import indySdk from 'indy-sdk' import { Subject } from 'rxjs' +import { IndySdkWallet } from '../../../../../indy-sdk/src' import { getAgentConfig, getAgentContext, @@ -21,7 +23,6 @@ import { signData, unpackAndVerifySignatureDecorator } from '../../../decorators import { JsonTransformer } from '../../../utils/JsonTransformer' import { indyDidFromPublicKeyBase58 } from '../../../utils/did' import { uuid } from '../../../utils/uuid' -import { IndyWallet } from '../../../wallet/IndyWallet' import { AckMessage, AckStatus } from '../../common' import { DidKey, IndyAgentService } from '../../dids' import { DidDocumentRole } from '../../dids/domain/DidDocumentRole' @@ -81,10 +82,9 @@ describe('ConnectionService', () => { let agentContext: AgentContext beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) + wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) agentContext = getAgentContext({ wallet, agentConfig }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) + await wallet.createAndOpen(agentConfig.walletConfig) }) afterAll(async () => { diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts index f51328bede..d82af72e21 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts @@ -6,7 +6,7 @@ import { DependencyManager } from '../../../plugins/DependencyManager' import { CredentialsApi } from '../CredentialsApi' import { CredentialsModule } from '../CredentialsModule' import { CredentialsModuleConfig } from '../CredentialsModuleConfig' -import { V1CredentialProtocol, V2CredentialProtocol } from '../protocol' +import { V2CredentialProtocol } from '../protocol' import { RevocationNotificationService } from '../protocol/revocation-notification/services' import { CredentialRepository } from '../repository' @@ -54,10 +54,7 @@ describe('CredentialsModule', () => { test('registers V1CredentialProtocol and V2CredentialProtocol if no credentialProtocols are configured', () => { const credentialsModule = new CredentialsModule() - expect(credentialsModule.config.credentialProtocols).toEqual([ - expect.any(V1CredentialProtocol), - expect.any(V2CredentialProtocol), - ]) + expect(credentialsModule.config.credentialProtocols).toEqual([expect.any(V2CredentialProtocol)]) }) test('calls register on the provided CredentialProtocols', () => { diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts index c0b3e57904..ce465af2a2 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts @@ -32,9 +32,7 @@ describe('RevocationNotificationService', () => { let eventEmitter: EventEmitter beforeEach(() => { - const agentConfig = getAgentConfig('RevocationNotificationService', { - indyLedgers: [], - }) + const agentConfig = getAgentConfig('RevocationNotificationService') agentContext = getAgentContext() diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts index 96b91c5a61..7ba534d046 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolCred.test.ts @@ -1,7 +1,13 @@ -import type { IndyCredentialViewMetadata } from '../../../../..' +/* eslint-disable @typescript-eslint/no-unused-vars */ import type { AgentContext } from '../../../../../agent' import type { GetAgentMessageOptions } from '../../../../../storage' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' +import type { + CredentialFormat, + CredentialFormatAcceptRequestOptions, + CredentialFormatCreateOfferOptions, + CredentialFormatService, +} from '../../../formats' import type { CredentialPreviewAttribute } from '../../../models/CredentialPreviewAttribute' import type { CustomCredentialTags } from '../../../repository/CredentialExchangeRecord' @@ -21,16 +27,11 @@ import { ConnectionService } from '../../../../connections/services/ConnectionSe import { CredentialEventTypes } from '../../../CredentialEvents' import { credReq } from '../../../__tests__/fixtures' import { CredentialProblemReportReason } from '../../../models/CredentialProblemReportReason' -import { IndyCredentialFormatService } from '../../../formats' -import { IndyCredentialUtils } from '../../../formats/indy/IndyCredentialUtils' -import { JsonLdCredentialFormatService } from '../../../formats/jsonld/JsonLdCredentialFormatService' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -import { CredentialMetadataKeys } from '../../../repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../repository/CredentialRepository' -import { V1CredentialPreview } from '../../v1/messages/V1CredentialPreview' import { V2CredentialProtocol } from '../V2CredentialProtocol' -import { V2ProposeCredentialMessage } from '../messages' +import { V2CredentialPreview, V2ProposeCredentialMessage } from '../messages' import { V2CredentialAckMessage } from '../messages/V2CredentialAckMessage' import { V2CredentialProblemReportMessage } from '../messages/V2CredentialProblemReportMessage' import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' @@ -39,8 +40,6 @@ import { V2RequestCredentialMessage } from '../messages/V2RequestCredentialMessa // Mock classes jest.mock('../../../repository/CredentialRepository') -jest.mock('../../../formats/jsonld/JsonLdCredentialFormatService') -jest.mock('../../../formats/indy/IndyCredentialFormatService') jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') jest.mock('../../../../routing/services/RoutingService') jest.mock('../../../../connections/services/ConnectionService') @@ -48,28 +47,13 @@ jest.mock('../../../../../agent/Dispatcher') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock -const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock -const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() -const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() const connectionService = new ConnectionServiceMock() -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -indyCredentialFormatService.formatKey = 'indy' -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -indyCredentialFormatService.credentialRecordType = 'indy' - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -jsonLdCredentialFormatService.formatKey = 'jsonld' - const agentConfig = getAgentConfig('V2CredentialProtocolCredTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -88,11 +72,6 @@ const connection = getMockConnection({ state: DidExchangeState.Completed, }) -const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', -}) - const offerAttachment = new Attachment({ id: 'offer-attachment-id', mimeType: 'application/json', @@ -115,7 +94,7 @@ const credentialAttachment = new Attachment({ mimeType: 'application/json', data: new AttachmentData({ base64: JsonEncoder.toBase64({ - values: IndyCredentialUtils.convertAttributesToValues(credentialPreview.attributes), + values: {}, }), }), }) @@ -162,7 +141,9 @@ credentialRequestMessage.setThread({ threadId: 'somethreadid' }) const credentialOfferMessage = new V2OfferCredentialMessage({ formats: [offerFormat], comment: 'some comment', - credentialPreview: credentialPreview, + credentialPreview: new V2CredentialPreview({ + attributes: [], + }), offerAttachments: [offerAttachment], }) const credentialIssueMessage = new V2IssueCredentialMessage({ @@ -199,7 +180,6 @@ const getAgentMessageMock = async (agentContext: AgentContext, options: GetAgent // object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. const mockCredentialRecord = ({ state, - metadata, threadId, connectionId, tags, @@ -207,7 +187,6 @@ const mockCredentialRecord = ({ credentialAttributes, }: { state?: CredentialState - metadata?: IndyCredentialViewMetadata & { indyRequest: Record } tags?: CustomCredentialTags threadId?: string connectionId?: string @@ -216,7 +195,7 @@ const mockCredentialRecord = ({ } = {}) => { const credentialRecord = new CredentialExchangeRecord({ id, - credentialAttributes: credentialAttributes || credentialPreview.attributes, + credentialAttributes: credentialAttributes, state: state || CredentialState.OfferSent, threadId: threadId || 'thread-id', connectionId: connectionId ?? '123', @@ -230,25 +209,34 @@ const mockCredentialRecord = ({ protocolVersion: 'v2', }) - if (metadata?.indyRequest) { - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) - } - - if (metadata?.schemaId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - schemaId: metadata.schemaId, - }) - } - - if (metadata?.credentialDefinitionId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - credentialDefinitionId: metadata.credentialDefinitionId, - }) - } - return credentialRecord } +interface TestCredentialFormat extends CredentialFormat { + formatKey: 'test' + credentialRecordType: 'test' +} + +type TestCredentialFormatService = CredentialFormatService + +export const testCredentialFormatService = { + credentialRecordType: 'test', + formatKey: 'test', + supportsFormat: (_format: string) => true, + createOffer: async ( + _agentContext: AgentContext, + _options: CredentialFormatCreateOfferOptions + ) => ({ + attachment: offerAttachment, + format: offerFormat, + }), + acceptRequest: async ( + _agentContext: AgentContext, + _options: CredentialFormatAcceptRequestOptions + ) => ({ attachment: credentialAttachment, format: credentialFormat }), + deleteCredentialById: jest.fn() as CredentialFormatService['deleteCredentialById'], +} as TestCredentialFormatService + describe('credentialProtocol', () => { let credentialProtocol: V2CredentialProtocol @@ -264,7 +252,7 @@ describe('credentialProtocol', () => { ]) credentialProtocol = new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormatService, jsonLdCredentialFormatService], + credentialFormats: [testCredentialFormatService], }) }) @@ -280,21 +268,10 @@ describe('credentialProtocol', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ - attachment: requestAttachment, - format: requestFormat, - }) - // when await credentialProtocol.acceptOffer(agentContext, { credentialRecord, - credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - }, - }, + credentialFormats: {}, }) // then @@ -317,12 +294,6 @@ describe('credentialProtocol', () => { connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', }) - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptOffer).mockResolvedValue({ - attachment: requestAttachment, - format: requestFormat, - }) - // when const { message: credentialRequest } = await credentialProtocol.acceptOffer(agentContext, { credentialRecord, @@ -357,8 +328,6 @@ describe('credentialProtocol', () => { describe('processRequest', () => { test(`updates state to ${CredentialState.RequestReceived}, set request and returns credential record`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferSent }) const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, @@ -381,8 +350,6 @@ describe('credentialProtocol', () => { }) test(`emits stateChange event from ${CredentialState.OfferSent} to ${CredentialState.RequestReceived}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferSent }) const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, @@ -413,8 +380,6 @@ describe('credentialProtocol', () => { const validState = CredentialState.OfferSent const invalidCredentialStates = Object.values(CredentialState).filter((state) => state !== validState) test(`throws an error when state transition is invalid`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const messageContext = new InboundMessageContext(credentialRequestMessage, { connection, agentContext, @@ -435,8 +400,7 @@ describe('credentialProtocol', () => { describe('acceptRequest', () => { test(`updates state to ${CredentialState.CredentialIssued}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(testCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: credentialFormat, }) @@ -462,8 +426,7 @@ describe('credentialProtocol', () => { }) test(`emits stateChange event from ${CredentialState.RequestReceived} to ${CredentialState.CredentialIssued}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(testCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: credentialFormat, }) @@ -501,8 +464,7 @@ describe('credentialProtocol', () => { }) test('returns credential response message base on credential request message', async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ + mockFunction(testCredentialFormatService.acceptRequest).mockResolvedValue({ attachment: credentialAttachment, format: credentialFormat, }) @@ -539,8 +501,6 @@ describe('credentialProtocol', () => { describe('processCredential', () => { test('finds credential record by thread ID and saves credential attachment into the wallet', async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestSent, }) @@ -808,8 +768,8 @@ describe('credentialProtocol', () => { expect(repositoryDeleteSpy).toHaveBeenNthCalledWith(1, agentContext, credentialRecord) }) - it('should call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + it('should call deleteCredentialById in testCredentialFormatService if deleteAssociatedCredential is true', async () => { + const deleteCredentialMock = mockFunction(testCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -826,8 +786,8 @@ describe('credentialProtocol', () => { ) }) - it('should not call deleteCredentialById in indyCredentialFormatService if deleteAssociatedCredential is false', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + it('should not call deleteCredentialById in testCredentialFormatService if deleteAssociatedCredential is false', async () => { + const deleteCredentialMock = mockFunction(testCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -841,7 +801,7 @@ describe('credentialProtocol', () => { }) it('deleteAssociatedCredentials should default to true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(testCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) @@ -855,7 +815,7 @@ describe('credentialProtocol', () => { ) }) it('deleteAssociatedDidCommMessages should default to true', async () => { - const deleteCredentialMock = mockFunction(indyCredentialFormatService.deleteCredentialById) + const deleteCredentialMock = mockFunction(testCredentialFormatService.deleteCredentialById) const credentialRecord = mockCredentialRecord() mockFunction(credentialRepository.getById).mockResolvedValue(credentialRecord) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts index 5ae8e56632..f1c3b5fb9c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts @@ -1,4 +1,6 @@ +import type { AgentContext } from '../../../../../agent' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' +import type { CredentialFormat, CredentialFormatCreateOfferOptions, CredentialFormatService } from '../../../formats' import type { CreateCredentialOfferOptions } from '../../CredentialProtocolOptions' import { Subject } from 'rxjs' @@ -12,25 +14,52 @@ import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils' import { DidExchangeState } from '../../../../connections' import { ConnectionService } from '../../../../connections/services/ConnectionService' -import { IndyLedgerService } from '../../../../ledger/services' import { RoutingService } from '../../../../routing/services/RoutingService' import { CredentialEventTypes } from '../../../CredentialEvents' -import { credDef, schema } from '../../../__tests__/fixtures' -import { IndyCredentialFormatService } from '../../../formats/indy/IndyCredentialFormatService' -import { JsonLdCredentialFormatService } from '../../../formats/jsonld/JsonLdCredentialFormatService' import { CredentialFormatSpec } from '../../../models' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { CredentialRepository } from '../../../repository/CredentialRepository' -import { V1CredentialPreview } from '../../v1/messages/V1CredentialPreview' import { V2CredentialProtocol } from '../V2CredentialProtocol' +import { V2CredentialPreview } from '../messages' import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' +const offerFormat = new CredentialFormatSpec({ + attachmentId: 'offer-attachment-id', + format: 'hlindy/cred-abstract@v2.0', +}) + +const offerAttachment = new Attachment({ + id: 'offer-attachment-id', + mimeType: 'application/json', + data: new AttachmentData({ + base64: + 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', + }), +}) + +interface TestCredentialFormat extends CredentialFormat { + formatKey: 'test' + credentialRecordType: 'test' +} + +type TestCredentialFormatService = CredentialFormatService + +export const testCredentialFormatService = { + credentialRecordType: 'test', + formatKey: 'test', + supportsFormat: (_format: string) => true, + createOffer: async ( + _agentContext: AgentContext, + _options: CredentialFormatCreateOfferOptions + ) => ({ + attachment: offerAttachment, + format: offerFormat, + }), +} as TestCredentialFormatService + // Mock classes jest.mock('../../../repository/CredentialRepository') -jest.mock('../../../../ledger/services/IndyLedgerService') -jest.mock('../../../formats/indy/IndyCredentialFormatService') -jest.mock('../../../formats/jsonld/JsonLdCredentialFormatService') jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') jest.mock('../../../../routing/services/RoutingService') jest.mock('../../../../connections/services/ConnectionService') @@ -38,9 +67,6 @@ jest.mock('../../../../../agent/Dispatcher') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock -const IndyLedgerServiceMock = IndyLedgerService as jest.Mock -const IndyCredentialFormatServiceMock = IndyCredentialFormatService as jest.Mock -const JsonLdCredentialFormatServiceMock = JsonLdCredentialFormatService as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock @@ -49,19 +75,9 @@ const DispatcherMock = Dispatcher as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() const routingService = new RoutingServiceMock() -const indyLedgerService = new IndyLedgerServiceMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() -const jsonLdCredentialFormatService = new JsonLdCredentialFormatServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -indyCredentialFormatService.formatKey = 'indy' - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -jsonLdCredentialFormatService.formatKey = 'jsonld' const agentConfig = getAgentConfig('V2CredentialProtocolOfferTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -70,7 +86,6 @@ const agentContext = getAgentContext({ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], [RoutingService, routingService], - [IndyLedgerService, indyLedgerService], [Dispatcher, dispatcher], [ConnectionService, connectionService], [EventEmitter, eventEmitter], @@ -83,35 +98,15 @@ const connectionRecord = getMockConnection({ state: DidExchangeState.Completed, }) -const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', -}) -const offerFormat = new CredentialFormatSpec({ - attachmentId: 'offer-attachment-id', - format: 'hlindy/cred-abstract@v2.0', -}) - -const offerAttachment = new Attachment({ - id: 'offer-attachment-id', - mimeType: 'application/json', - data: new AttachmentData({ - base64: - 'eyJzY2hlbWFfaWQiOiJhYWEiLCJjcmVkX2RlZl9pZCI6IlRoN01wVGFSWlZSWW5QaWFiZHM4MVk6MzpDTDoxNzpUQUciLCJub25jZSI6Im5vbmNlIiwia2V5X2NvcnJlY3RuZXNzX3Byb29mIjp7fX0', - }), -}) - describe('V2CredentialProtocolOffer', () => { let credentialProtocol: V2CredentialProtocol beforeEach(async () => { // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connectionRecord) - mockFunction(indyLedgerService.getCredentialDefinition).mockResolvedValue(credDef) - mockFunction(indyLedgerService.getSchema).mockResolvedValue(schema) credentialProtocol = new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormatService, jsonLdCredentialFormatService], + credentialFormats: [testCredentialFormatService], }) }) @@ -120,24 +115,15 @@ describe('V2CredentialProtocolOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateCredentialOfferOptions<[IndyCredentialFormatService]> = { + const offerOptions: CreateCredentialOfferOptions<[TestCredentialFormatService]> = { comment: 'some comment', connectionRecord, credentialFormats: { - indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: 'Th7MpTaRZVRYnPiabds81Y:3:CL:17:TAG', - }, + test: {}, }, } test(`creates credential record in ${CredentialState.OfferSent} state with offer, thread ID`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.createOffer).mockResolvedValue({ - attachment: offerAttachment, - format: offerFormat, - }) - // when await credentialProtocol.createOffer(agentContext, offerOptions) @@ -156,12 +142,6 @@ describe('V2CredentialProtocolOffer', () => { }) test(`emits stateChange event with a new credential in ${CredentialState.OfferSent} state`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.createOffer).mockResolvedValue({ - attachment: offerAttachment, - format: offerFormat, - }) - const eventListenerMock = jest.fn() eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) @@ -182,13 +162,6 @@ describe('V2CredentialProtocolOffer', () => { }) test('returns credential offer message', async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.createOffer).mockResolvedValue({ - attachment: offerAttachment, - format: offerFormat, - previewAttributes: credentialPreview.attributes, - }) - const { message: credentialOffer } = await credentialProtocol.createOffer(agentContext, offerOptions) expect(credentialOffer.toJSON()).toMatchObject({ @@ -220,7 +193,9 @@ describe('V2CredentialProtocolOffer', () => { const credentialOfferMessage = new V2OfferCredentialMessage({ formats: [offerFormat], comment: 'some comment', - credentialPreview, + credentialPreview: new V2CredentialPreview({ + attributes: [], + }), offerAttachments: [offerAttachment], }) @@ -230,8 +205,6 @@ describe('V2CredentialProtocolOffer', () => { }) test(`creates and return credential record in ${CredentialState.OfferReceived} state with offer, thread ID`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - // when await credentialProtocol.processOffer(messageContext) @@ -251,8 +224,6 @@ describe('V2CredentialProtocolOffer', () => { }) test(`emits stateChange event with ${CredentialState.OfferReceived}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - const eventListenerMock = jest.fn() eventEmitter.on(CredentialEventTypes.CredentialStateChanged, eventListenerMock) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index 974cef2c84..8116f08d9f 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -1,4 +1,5 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '../../../CredentialsApiOptions' @@ -7,10 +8,10 @@ import { ReplaySubject, Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' import { - prepareForIndyIssuance, - waitForCredentialRecordSubject, - getAgentOptions, -} from '../../../../../../tests/helpers' + getLegacyAnonCredsModules, + prepareForAnonCredsIssuance, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForCredentialRecordSubject, getAgentOptions } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { Agent } from '../../../../../agent/Agent' import { CredentialEventTypes } from '../../../CredentialEvents' @@ -19,13 +20,21 @@ import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { V2CredentialPreview } from '../messages' -const faberAgentOptions = getAgentOptions('Faber connection-less Credentials V2', { - endpoints: ['rxjs:faber'], -}) - -const aliceAgentOptions = getAgentOptions('Alice connection-less Credentials V2', { - endpoints: ['rxjs:alice'], -}) +const faberAgentOptions = getAgentOptions( + 'Faber connection-less Credentials V2', + { + endpoints: ['rxjs:faber'], + }, + getLegacyAnonCredsModules() +) + +const aliceAgentOptions = getAgentOptions( + 'Alice connection-less Credentials V2', + { + endpoints: ['rxjs:alice'], + }, + getLegacyAnonCredsModules() +) const credentialPreview = V2CredentialPreview.fromRecord({ name: 'John', @@ -33,8 +42,8 @@ const credentialPreview = V2CredentialPreview.fromRecord({ }) describe('V2 Connectionless Credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent let faberReplay: ReplaySubject let aliceReplay: ReplaySubject let credentialDefinitionId: string @@ -57,8 +66,11 @@ describe('V2 Connectionless Credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age']) - credentialDefinitionId = definition.id + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + issuerId: faberAgent.publicDid?.did as string, + attributeNames: ['name', 'age'], + }) + credentialDefinitionId = credentialDefinition.credentialDefinitionId faberReplay = new ReplaySubject() aliceReplay = new ReplaySubject() diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts index 0bd25c345e..6d23c2d6d5 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts @@ -1,9 +1,9 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' import type { AcceptCredentialOfferOptions, AcceptCredentialProposalOptions } from '../../../CredentialsApiOptions' import type { Schema } from 'indy-sdk' -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import { setupAnonCredsTests } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' @@ -11,13 +11,14 @@ import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' import { V2CredentialPreview } from '../messages/V2CredentialPreview' -describe('v2 credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string +describe('V2 Credentials Auto Accept', () => { + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let credentialDefinitionId: string let schema: Schema - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord + let faberConnectionId: string + let aliceConnectionId: string + const credentialPreview = V2CredentialPreview.fromRecord({ name: 'John', age: '99', @@ -31,13 +32,20 @@ describe('v2 credentials', () => { profile_picture: 'another profile picture', }) - describe('Auto accept on `always`', () => { + describe("Auto accept on 'always'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: always v2', - 'alice agent: always v2', - AutoAcceptCredential.Always - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'faber agent: always v2', + holderName: 'alice agent: always v2', + autoAcceptCredentials: AutoAcceptCredential.Always, + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) }) afterAll(async () => { @@ -47,7 +55,7 @@ describe('v2 credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Alice begins listening for credential') const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { state: CredentialState.CredentialReceived, @@ -60,12 +68,12 @@ describe('v2 credentials', () => { testLogger.test('Alice sends credential proposal to Faber') await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v2 propose credential test', @@ -85,7 +93,7 @@ describe('v2 credentials', () => { data: { '_internal/indyCredential': { schemaId: schema.id, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -93,7 +101,7 @@ describe('v2 credentials', () => { }) }) - test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Alice begins listening for credential') const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { state: CredentialState.CredentialReceived, @@ -108,11 +116,11 @@ describe('v2 credentials', () => { const schemaId = schema.id await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', @@ -128,7 +136,7 @@ describe('v2 credentials', () => { '_internal/indyRequest': expect.any(Object), '_internal/indyCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -152,13 +160,21 @@ describe('v2 credentials', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + describe("Auto accept on 'contentApproved'", () => { + // FIXME: we don't need to set up the agent and create all schemas/credential definitions again, just change the auto accept credential setting beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, schema, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: contentApproved v2', - 'alice agent: contentApproved v2', - AutoAcceptCredential.ContentApproved - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Agent: Always V2', + holderName: 'Alice Agent: Always V2', + autoAcceptCredentials: AutoAcceptCredential.Always, + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) }) afterAll(async () => { @@ -168,7 +184,7 @@ describe('v2 credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Alice sends credential proposal to Faber') const schemaId = schema.id @@ -178,12 +194,12 @@ describe('v2 credentials', () => { }) await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }) @@ -206,7 +222,7 @@ describe('v2 credentials', () => { comment: 'V2 Indy Offer', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, @@ -227,7 +243,7 @@ describe('v2 credentials', () => { '_internal/indyRequest': expect.any(Object), '_internal/indyCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -251,7 +267,7 @@ describe('v2 credentials', () => { data: { '_internal/indyCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -259,7 +275,7 @@ describe('v2 credentials', () => { }) }) - test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Alice starts listening for credential offer from Faber') const aliceOfferReceivedPromise = waitForCredentialRecord(aliceAgent, { state: CredentialState.OfferReceived, @@ -269,11 +285,11 @@ describe('v2 credentials', () => { const schemaId = schema.id await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', @@ -291,7 +307,7 @@ describe('v2 credentials', () => { expect(aliceOfferReceivedRecord.getTags()).toEqual({ threadId: aliceOfferReceivedRecord.threadId, state: aliceOfferReceivedRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) testLogger.test('Alice received credential offer from Faber') @@ -322,7 +338,7 @@ describe('v2 credentials', () => { '_internal/indyRequest': expect.any(Object), '_internal/indyCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -346,7 +362,7 @@ describe('v2 credentials', () => { }) }) - test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Faber starts listening for proposal from Alice') const faberPropReceivedPromise = waitForCredentialRecord(faberAgent, { state: CredentialState.ProposalReceived, @@ -354,12 +370,12 @@ describe('v2 credentials', () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredProposal = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v2 propose credential test', @@ -379,7 +395,7 @@ describe('v2 credentials', () => { credentialRecordId: faberPropReceivedRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -393,7 +409,7 @@ describe('v2 credentials', () => { expect(aliceOfferReceivedRecord.getTags()).toEqual({ threadId: aliceOfferReceivedRecord.threadId, state: aliceOfferReceivedRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) @@ -405,7 +421,7 @@ describe('v2 credentials', () => { aliceRecord.assertState(CredentialState.OfferReceived) }) - test('Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Alice starts listening for offer from Faber') const aliceCredentialExchangeRecordPromise = waitForCredentialRecord(aliceAgent, { state: CredentialState.OfferReceived, @@ -414,11 +430,11 @@ describe('v2 credentials', () => { testLogger.test('Faber sends credential offer to Alice') await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', @@ -432,7 +448,7 @@ describe('v2 credentials', () => { expect(aliceOfferReceivedRecord.getTags()).toEqual({ threadId: aliceOfferReceivedRecord.threadId, state: aliceOfferReceivedRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) @@ -447,7 +463,7 @@ describe('v2 credentials', () => { credentialFormats: { indy: { attributes: newCredentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v2 propose credential test', diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts index 10c0ae5827..202368c089 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts @@ -1,12 +1,14 @@ -import type { ReplaySubject } from 'rxjs' -import type { ConnectionRecord } from '../../../../connections' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' +import type { AnonCredsHolderService } from '../../../../../../../anoncreds/src' +import type { LegacyIndyProposeCredentialFormat } from '../../../../../../../anoncreds/src/formats/LegacyIndyCredentialFormat' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' +import { AnonCredsHolderServiceSymbol } from '../../../../../../../anoncreds/src' import { - setupCredentialTests, - waitForCredentialRecord, - waitForCredentialRecordSubject, -} from '../../../../../../tests/helpers' + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForCredentialRecord, waitForCredentialRecordSubject } from '../../../../../../tests' import testLogger from '../../../../../../tests/logger' import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils' @@ -20,15 +22,6 @@ import { V2RequestCredentialMessage, } from '../messages' -import { AnonCredsHolderService, AnonCredsHolderServiceSymbol } from '../../../../../../../anoncreds/src' -import { - AnonCredsAgent, - issueLegacyAnonCredsCredential, -} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' - -// TODO: what to export from anoncreds root package? -import { LegacyIndyProposeCredentialFormat } from '../../../../../../../anoncreds/src/formats/LegacyIndyCredentialFormat' - const credentialPreview = V2CredentialPreview.fromRecord({ name: 'John', age: '99', @@ -37,17 +30,16 @@ const credentialPreview = V2CredentialPreview.fromRecord({ }) describe('v2 credentials', () => { - let faberAgent: AnonCredsAgent - let aliceAgent: AnonCredsAgent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let faberCredentialRecord: CredentialExchangeRecord - let faberReplay: ReplaySubject - let aliceReplay: ReplaySubject - - let credPropose: LegacyIndyProposeCredentialFormat + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let credentialDefinitionId: string + let faberConnectionId: string + let aliceConnectionId: string + + let faberReplay: EventReplaySubject + let aliceReplay: EventReplaySubject + + let indyCredentialProposal: LegacyIndyProposeCredentialFormat const newCredentialPreview = V2CredentialPreview.fromRecord({ name: 'John', @@ -57,11 +49,22 @@ describe('v2 credentials', () => { }) beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, faberReplay, aliceReplay } = - await setupCredentialTests('Faber Agent Credentials v2', 'Alice Agent Credentials v2')) - - credPropose = { - credentialDefinitionId: credDefId, + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Agent Credentials v2', + holderName: 'Alice Agent Credentials v2', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) + + indyCredentialProposal = { + credentialDefinitionId: credentialDefinitionId, schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', schemaName: 'ahoy', schemaVersion: '1.0', @@ -81,7 +84,7 @@ describe('v2 credentials', () => { testLogger.test('Alice sends (v2) credential proposal to Faber') const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { @@ -98,14 +101,14 @@ describe('v2 credentials', () => { }) expect(credentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', state: CredentialState.ProposalSent, threadId: expect.any(String), }) testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -116,14 +119,14 @@ describe('v2 credentials', () => { comment: 'V2 Indy Proposal', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) @@ -185,7 +188,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', state: CredentialState.RequestSent, threadId: expect.any(String), @@ -221,12 +224,14 @@ describe('v2 credentials', () => { }) test('Faber issues credential which is then deleted from Alice`s wallet', async () => { - const { holderCredentialExchangeRecord, issuerCredentialExchangeRecord } = await issueLegacyAnonCredsCredential({ + const { holderCredentialExchangeRecord } = await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, - issuerConnectionId: faberConnection.id, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, holderAgent: aliceAgent, - credentialTemplate: { - credentialDefinitionId: credDefId, + holderReplay: aliceReplay, + offer: { + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }) @@ -261,11 +266,11 @@ describe('v2 credentials', () => { testLogger.test('Alice sends credential proposal to Faber') let aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: credentialPreview.attributes, }, }, @@ -285,7 +290,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -311,7 +316,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -331,7 +336,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -346,7 +351,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, state: CredentialState.RequestSent, protocolVersion: 'v2', threadId: aliceCredentialExchangeRecord.threadId, @@ -396,18 +401,18 @@ describe('v2 credentials', () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await aliceCredentialRecordPromise + let aliceCredentialRecord = await aliceCredentialRecordPromise let faberCredentialRecordPromise = waitForCredentialRecord(faberAgent, { threadId: aliceCredentialRecord.threadId, @@ -418,7 +423,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -437,7 +442,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -456,7 +461,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -478,7 +483,7 @@ describe('v2 credentials', () => { comment: 'V2 Indy Proposal', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, @@ -497,7 +502,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, state: CredentialState.RequestSent, protocolVersion: 'v2', }) @@ -635,18 +640,18 @@ describe('v2 credentials', () => { testLogger.test('Faber sends credential offer to Alice') const faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index 55cb8c273c..475bb2058b 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -1,73 +1,15 @@ -import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import type { Wallet } from '../../../../../wallet' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { EventReplaySubject, JsonLdTestsAgent } from '../../../../../../tests' import type { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' -import { ReplaySubject, Subject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' -import { - getAgentOptions, - prepareForIndyIssuance, - waitForCredentialRecordSubject, -} from '../../../../../../tests/helpers' +import { setupJsonLdTests, waitForCredentialRecordSubject } from '../../../../../../tests' import testLogger from '../../../../../../tests/logger' -import { Agent } from '../../../../../agent/Agent' -import { InjectionSymbols } from '../../../../../constants' import { KeyType } from '../../../../../crypto' -import { JsonEncoder } from '../../../../../utils/JsonEncoder' -import { W3cVcModule } from '../../../../vc' -import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader' import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' -import { CredentialEventTypes } from '../../../CredentialEvents' -import { CredentialsModule } from '../../../CredentialsModule' -import { JsonLdCredentialFormatService } from '../../../formats' import { CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository' -import { V2CredentialProtocol } from '../V2CredentialProtocol' - -const faberAgentOptions = getAgentOptions( - 'Faber LD connection-less Credentials V2', - { - endpoints: ['rxjs:faber'], - }, - { - credentials: new CredentialsModule({ - credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], - }), - w3cVc: new W3cVcModule({ - documentLoader: customDocumentLoader, - }), - } -) - -const aliceAgentOptions = getAgentOptions( - 'Alice LD connection-less Credentials V2', - { - endpoints: ['rxjs:alice'], - }, - { - credentials: new CredentialsModule({ - credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], - }), - w3cVc: new W3cVcModule({ - documentLoader: customDocumentLoader, - }), - } -) - -let wallet -let signCredentialOptions: JsonLdCredentialDetailFormat -describe('credentials', () => { - let faberAgent: Agent<(typeof faberAgentOptions)['modules']> - let aliceAgent: Agent<(typeof aliceAgentOptions)['modules']> - let faberReplay: ReplaySubject - let aliceReplay: ReplaySubject - const seed = 'testseed000000000000000000000001' - const TEST_LD_DOCUMENT: JsonCredential = { +const signCredentialOptions = { + credential: { '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], type: ['VerifiableCredential', 'UniversityDegreeCredential'], issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', @@ -78,47 +20,32 @@ describe('credentials', () => { name: 'Bachelor of Science and Arts', }, }, - } + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, +} + +describe('credentials', () => { + let faberAgent: JsonLdTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: JsonLdTestsAgent + let aliceReplay: EventReplaySubject + beforeEach(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() - - await prepareForIndyIssuance(faberAgent, ['name', 'age']) - - faberReplay = new ReplaySubject() - aliceReplay = new ReplaySubject() - - faberAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(faberReplay) - aliceAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(aliceReplay) - wallet = faberAgent.dependencyManager.resolve(InjectionSymbols.Wallet) - - await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) - - signCredentialOptions = { - credential: TEST_LD_DOCUMENT, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + } = await setupJsonLdTests({ + issuerName: 'Faber LD connection-less Credentials V2', + holderName: 'Alice LD connection-less Credentials V2', + createConnections: false, + })) + + await faberAgent.context.wallet.createKey({ seed: 'testseed000000000000000000000001', keyType: KeyType.Ed25519 }) }) afterEach(async () => { @@ -140,36 +67,34 @@ describe('credentials', () => { protocolVersion: 'v2', }) - const offerMsg = message as V2OfferCredentialMessage - const attachment = offerMsg?.offerAttachments[0] - - if (attachment.data.base64) { - expect(JsonEncoder.fromBase64(attachment.data.base64)).toMatchObject({ - credential: { - '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], - type: ['VerifiableCredential', 'UniversityDegreeCredential'], - issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - issuanceDate: '2017-10-22T12:23:48Z', - credentialSubject: { - degree: { - name: 'Bachelor of Science and Arts', - type: 'BachelorDegree', - }, + const offerMessage = message as V2OfferCredentialMessage + const attachment = offerMessage?.offerAttachments[0] + + expect(attachment?.getDataAsJson()).toMatchObject({ + credential: { + '@context': ['https://www.w3.org/2018/credentials/v1', 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + name: 'Bachelor of Science and Arts', + type: 'BachelorDegree', }, }, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - }) - } + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, + }) - const { message: offerMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + const { message: connectionlessOfferMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ recordId: faberCredentialRecord.id, message, domain: 'https://a-domain.com', }) - await aliceAgent.receiveMessage(offerMessage.toJSON()) + await aliceAgent.receiveMessage(connectionlessOfferMessage.toJSON()) let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index ad08852a17..1cb4c24de0 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -1,58 +1,55 @@ -import type { CredentialTestsAgent } from '../../../../../../tests/helpers' -import type { Wallet } from '../../../../../wallet' -import type { ConnectionRecord } from '../../../../connections' -import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' +import type { JsonLdTestsAgent } from '../../../../../../tests' -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import { setupJsonLdTests } from '../../../../../../tests' +import { waitForCredentialRecord } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' -import { InjectionSymbols } from '../../../../../constants' import { KeyType } from '../../../../../crypto' import { AriesFrameworkError } from '../../../../../error/AriesFrameworkError' import { CREDENTIALS_CONTEXT_V1_URL } from '../../../../vc/constants' import { AutoAcceptCredential, CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' -const TEST_LD_DOCUMENT: JsonCredential = { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], - type: ['VerifiableCredential', 'UniversityDegreeCredential'], - issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - issuanceDate: '2017-10-22T12:23:48Z', - credentialSubject: { - degree: { - type: 'BachelorDegree', - name: 'Bachelor of Science and Arts', +const signCredentialOptions = { + credential: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://www.w3.org/2018/credentials/examples/v1'], + type: ['VerifiableCredential', 'UniversityDegreeCredential'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2017-10-22T12:23:48Z', + credentialSubject: { + degree: { + type: 'BachelorDegree', + name: 'Bachelor of Science and Arts', + }, }, }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, } -describe('credentials', () => { - let faberAgent: CredentialTestsAgent - let aliceAgent: CredentialTestsAgent - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let signCredentialOptions: JsonLdCredentialDetailFormat - let wallet - const seed = 'testseed000000000000000000000001' - - describe('Auto accept on `always`', () => { +describe('V2 Credentials - JSON-LD - Auto Accept Always', () => { + let faberAgent: JsonLdTestsAgent + let aliceAgent: JsonLdTestsAgent + let faberConnectionId: string + let aliceConnectionId: string + + describe("Auto accept on 'always'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: always v2 jsonld', - 'alice agent: always v2 jsonld', - AutoAcceptCredential.Always - )) - - wallet = faberAgent.dependencyManager.resolve(InjectionSymbols.Wallet) - await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) - signCredentialOptions = { - credential: TEST_LD_DOCUMENT, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupJsonLdTests({ + issuerName: 'faber agent: always v2 jsonld', + holderName: 'alice agent: always v2 jsonld', + autoAcceptCredentials: AutoAcceptCredential.Always, + })) + + await faberAgent.context.wallet.createKey({ seed: 'testseed000000000000000000000001', keyType: KeyType.Ed25519 }) }) + afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -60,11 +57,11 @@ describe('credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `always`', async () => { + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -74,7 +71,7 @@ describe('credentials', () => { testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, }) @@ -92,19 +89,19 @@ describe('credentials', () => { state: CredentialState.Done, }) }) - test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `always`', async () => { + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Faber sends V2 credential offer to Alice as start of protocol process') const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { jsonld: signCredentialOptions, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -135,22 +132,20 @@ describe('credentials', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + describe("Auto accept on 'contentApproved'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, faberConnection, aliceConnection } = await setupCredentialTests( - 'faber agent: content-approved v2 jsonld', - 'alice agent: content-approved v2 jsonld', - AutoAcceptCredential.ContentApproved - )) - wallet = faberAgent.dependencyManager.resolve(InjectionSymbols.Wallet) - await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) - signCredentialOptions = { - credential: TEST_LD_DOCUMENT, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupJsonLdTests({ + issuerName: 'faber agent: ContentApproved v2 jsonld', + holderName: 'alice agent: ContentApproved v2 jsonld', + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + })) + + await faberAgent.context.wallet.createKey({ seed: 'testseed000000000000000000000001', keyType: KeyType.Ed25519 }) }) afterAll(async () => { @@ -160,10 +155,10 @@ describe('credentials', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on `contentApproved`', async () => { + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -184,7 +179,7 @@ describe('credentials', () => { }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + const aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, }) @@ -212,12 +207,12 @@ describe('credentials', () => { state: CredentialState.Done, }) }) - test('Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on `contentApproved`', async () => { + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Faber sends credential offer to Alice') let faberCredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { jsonld: signCredentialOptions, }, @@ -225,7 +220,7 @@ describe('credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -235,7 +230,7 @@ describe('credentials', () => { expect(aliceCredentialRecord.getTags()).toEqual({ threadId: aliceCredentialRecord.threadId, state: aliceCredentialRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) @@ -278,19 +273,19 @@ describe('credentials', () => { state: CredentialState.Done, }) }) - test('Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Faber sends credential offer to Alice') const faberCredentialExchangeRecord: CredentialExchangeRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', - connectionId: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { jsonld: signCredentialOptions, }, protocolVersion: 'v2', }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -300,7 +295,7 @@ describe('credentials', () => { expect(aliceCredentialRecord.getTags()).toEqual({ threadId: aliceCredentialRecord.threadId, state: aliceCredentialRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) @@ -339,10 +334,10 @@ describe('credentials', () => { aliceCredentialRecord.assertState(CredentialState.ProposalSent) }) - test('Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on `contentApproved` and attributes did change', async () => { + test("Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { testLogger.test('Alice sends credential proposal to Faber') const aliceCredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -385,7 +380,7 @@ describe('credentials', () => { expect(record.getTags()).toEqual({ threadId: record.threadId, state: record.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) expect(record.type).toBe(CredentialExchangeRecord.type) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index c8f9a64d20..a9f8a23d1c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -1,30 +1,50 @@ -import type { Awaited } from '../../../../../types' -import type { Wallet } from '../../../../../wallet' -import type { ConnectionRecord } from '../../../../connections' -import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../../formats/jsonld/JsonLdCredentialFormat' - -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { InjectionSymbols } from '../../../../../constants' +import type { EventReplaySubject } from '../../../../../../tests' + +import { randomUUID } from 'crypto' +import indySdk from 'indy-sdk' + +import { + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, + V1CredentialProtocol, + V1ProofProtocol, + AnonCredsModule, +} from '../../../../../../../anoncreds/src' +import { prepareForAnonCredsIssuance } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { + IndySdkAnonCredsRegistry, + IndySdkModule, + IndySdkSovDidRegistrar, + IndySdkSovDidResolver, +} from '../../../../../../../indy-sdk/src' +import { + setupEventReplaySubjects, + setupSubjectTransports, + genesisPath, + taaAcceptanceMechanism, + taaVersion, + getAgentOptions, + waitForCredentialRecordSubject, + testLogger, +} from '../../../../../../tests' +import { Agent } from '../../../../../agent/Agent' import { KeyType } from '../../../../../crypto' -import { DidCommMessageRepository } from '../../../../../storage' import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { CacheModule, InMemoryLruCache } from '../../../../cache' +import { DidsModule } from '../../../../dids' +import { ProofEventTypes, ProofsModule, V2ProofProtocol } from '../../../../proofs' +import { W3cVcModule } from '../../../../vc' +import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader' +import { CredentialEventTypes } from '../../../CredentialEvents' +import { CredentialsModule } from '../../../CredentialsModule' +import { JsonLdCredentialFormatService } from '../../../formats' import { CredentialState } from '../../../models' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' +import { V2CredentialProtocol } from '../V2CredentialProtocol' import { V2CredentialPreview } from '../messages' -import { V2IssueCredentialMessage } from '../messages/V2IssueCredentialMessage' -import { V2OfferCredentialMessage } from '../messages/V2OfferCredentialMessage' -describe('credentials', () => { - let faberAgent: Awaited>['faberAgent'] - let aliceAgent: Awaited>['aliceAgent'] - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let faberCredentialRecord: CredentialExchangeRecord - - let didCommMessageRepository: DidCommMessageRepository - - const inputDocAsJson: JsonCredential = { +const signCredentialOptions = { + credential: { '@context': [ 'https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', @@ -52,28 +72,87 @@ describe('credentials', () => { birthCountry: 'Bahamas', birthDate: '1958-07-17', }, - } - - let signCredentialOptions: JsonLdCredentialDetailFormat - - let wallet - const seed = 'testseed000000000000000000000001' - let credDefId: string + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, +} + +const indyCredentialFormat = new LegacyIndyCredentialFormatService() +const jsonLdCredentialFormat = new JsonLdCredentialFormatService() +const indyProofFormat = new LegacyIndyProofFormatService() + +const indyJsonLdModules = { + credentials: new CredentialsModule({ + credentialProtocols: [ + new V1CredentialProtocol({ indyCredentialFormat }), + new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormat, jsonLdCredentialFormat], + }), + ], + }), + proofs: new ProofsModule({ + proofProtocols: [ + new V1ProofProtocol({ indyProofFormat }), + new V2ProofProtocol({ + proofFormats: [indyProofFormat], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndySdkAnonCredsRegistry()], + }), + dids: new DidsModule({ + resolvers: [new IndySdkSovDidResolver()], + registrars: [new IndySdkSovDidRegistrar()], + }), + indySdk: new IndySdkModule({ + indySdk, + networks: [ + { + isProduction: false, + genesisPath, + id: randomUUID(), + indyNamespace: `pool:localtest`, + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, + }, + ], + }), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), + w3cVc: new W3cVcModule({ + documentLoader: customDocumentLoader, + }), +} as const + +// TODO: extract these very specific tests to the jsonld format +describe('V2 Credentials - JSON-LD - Ed25519', () => { + let faberAgent: Agent + let faberReplay: EventReplaySubject + let aliceAgent: Agent + let aliceReplay: EventReplaySubject + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, aliceConnection } = await setupCredentialTests( - 'Faber Agent Credentials LD', - 'Alice Agent Credentials LD' - )) - wallet = faberAgent.dependencyManager.resolve(InjectionSymbols.Wallet) - await wallet.createKey({ seed, keyType: KeyType.Ed25519 }) - signCredentialOptions = { - credential: inputDocAsJson, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + const faberAgent = new Agent(getAgentOptions('Faber Agent Indy/JsonLD', {}, indyJsonLdModules)) + const aliceAgent = new Agent(getAgentOptions('Alice Agent Indy/JsonLD', {}, indyJsonLdModules)) + + setupSubjectTransports([faberAgent, aliceAgent]) + ;[faberReplay, aliceReplay] = setupEventReplaySubjects( + [faberAgent, aliceAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], + issuerId: faberAgent.publicDid?.did as string, + }) + credentialDefinitionId = credentialDefinition.credentialDefinitionId + + await faberAgent.context.wallet.createKey({ seed: 'testseed000000000000000000000001', keyType: KeyType.Ed25519 }) }) afterAll(async () => { @@ -86,8 +165,8 @@ describe('credentials', () => { test('Alice starts with V2 (ld format, Ed25519 signature) credential proposal to Faber', async () => { testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') - const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -95,13 +174,13 @@ describe('credentials', () => { comment: 'v2 propose credential test for W3C Credentials', }) - expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnectionId) expect(credentialExchangeRecord.protocolVersion).toEqual('v2') expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) expect(credentialExchangeRecord.threadId).not.toBeNull() testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -113,18 +192,12 @@ describe('credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const offerMessage = await didCommMessageRepository.findAgentMessage(aliceAgent.context, { - associatedRecordId: aliceCredentialRecord.id, - messageClass: V2OfferCredentialMessage, - }) - + const offerMessage = await faberAgent.credentials.findOfferMessage(aliceCredentialRecord.id) expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', '@id': expect.any(String), @@ -162,93 +235,87 @@ describe('credentials', () => { expect(aliceCredentialRecord.id).not.toBeNull() expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) - if (aliceCredentialRecord.connectionId) { - const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ - credentialRecordId: aliceCredentialRecord.id, - credentialFormats: { - jsonld: {}, + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + credentialFormats: { + jsonld: {}, + }, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnectionId) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecordSubject(faberReplay, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await faberAgent.credentials.findCredentialMessage(faberCredentialRecord.id) + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 Indy Credential', + formats: [ + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@1.0', }, - }) - - expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) - expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') - expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) - expect(offerCredentialExchangeRecord.threadId).not.toBeNull() - - testLogger.test('Faber waits for credential request from Alice') - await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, - }) - - testLogger.test('Faber sends credential to Alice') - - await faberAgent.credentials.acceptRequest({ - credentialRecordId: faberCredentialRecord.id, - comment: 'V2 Indy Credential', - }) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Alice sends credential ack to Faber') - await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: expect.any(String), - connectionId: expect.any(String), - state: CredentialState.CredentialReceived, - }) - - const credentialMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberCredentialRecord.id, - messageClass: V2IssueCredentialMessage, - }) - - expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ - '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', - '@id': expect.any(String), - comment: 'V2 Indy Credential', - formats: [ - { - attach_id: expect.any(String), - format: 'aries/ld-proof-vc@1.0', - }, - ], - 'credentials~attach': [ - { - '@id': expect.any(String), - 'mime-type': 'application/json', - data: expect.any(Object), - lastmod_time: undefined, - byte_count: undefined, - }, - ], - '~thread': { - thid: expect.any(String), - pthid: undefined, - sender_order: undefined, - received_orders: undefined, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, }, - '~please_ack': { on: ['RECEIPT'] }, - '~service': undefined, - '~attach': undefined, - '~timing': undefined, - '~transport': undefined, - '~l10n': undefined, - }) - } + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) }) test('Multiple Formats: Alice starts with V2 (both ld and indy formats) credential proposal to Faber', async () => { @@ -260,35 +327,34 @@ describe('credentials', () => { 'x-ray': 'some x-ray', profile_picture: 'profile picture', }) - const testAttributes = { - attributes: credentialPreview.attributes, - schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', - schemaName: 'ahoy', - schemaVersion: '1.0', - schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', - issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', - credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', - } testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') - const credentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { - indy: testAttributes, + indy: { + attributes: credentialPreview.attributes, + schemaIssuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + schemaName: 'ahoy', + schemaVersion: '1.0', + schemaId: 'q7ATwTYbQDgiigVijUAej:2:test:1.0', + issuerDid: 'GMm4vMw8LLrLJjp81kRRLp', + credentialDefinitionId: 'GMm4vMw8LLrLJjp81kRRLp:3:CL:12:tag', + }, jsonld: signCredentialOptions, }, comment: 'v2 propose credential test', }) - expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnectionId) expect(credentialExchangeRecord.protocolVersion).toEqual('v2') expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) expect(credentialExchangeRecord.threadId).not.toBeNull() testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -300,7 +366,7 @@ describe('credentials', () => { comment: 'V2 W3C & INDY Proposals', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId, attributes: credentialPreview.attributes, }, jsonld: {}, // this is to ensure both services are formatted @@ -308,20 +374,14 @@ describe('credentials', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const offerMessage = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberCredentialRecord.id, - messageClass: V2OfferCredentialMessage, - }) - - const credOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() - expect(credOfferJson).toMatchObject({ + const offerMessage = await faberAgent.credentials.findOfferMessage(faberCredentialRecord.id) + const credentialOfferJson = offerMessage?.offerAttachments[1].getDataAsJson() + expect(credentialOfferJson).toMatchObject({ credential: { '@context': [ 'https://www.w3.org/2018/credentials/v1', @@ -407,139 +467,133 @@ describe('credentials', () => { expect(aliceCredentialRecord.id).not.toBeNull() expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) - if (aliceCredentialRecord.connectionId) { - const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ - credentialRecordId: aliceCredentialRecord.id, - }) - - expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) - expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') - expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) - expect(offerCredentialExchangeRecord.threadId).not.toBeNull() - - testLogger.test('Faber waits for credential request from Alice') - await waitForCredentialRecord(faberAgent, { - threadId: aliceCredentialRecord.threadId, - state: CredentialState.RequestReceived, - }) - - testLogger.test('Faber sends credential to Alice') - - await faberAgent.credentials.acceptRequest({ - credentialRecordId: faberCredentialRecord.id, - comment: 'V2 Indy Credential', - }) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Alice sends credential ack to Faber') - await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialRecord.threadId, - state: CredentialState.Done, - }) - expect(aliceCredentialRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - threadId: expect.any(String), - connectionId: expect.any(String), - state: CredentialState.CredentialReceived, - }) - - const credentialMessage = await didCommMessageRepository.getAgentMessage(faberAgent.context, { - associatedRecordId: faberCredentialRecord.id, - messageClass: V2IssueCredentialMessage, - }) - - const w3cCredential = credentialMessage.credentialAttachments[1].getDataAsJson() - - expect(w3cCredential).toMatchObject({ - context: [ - 'https://www.w3.org/2018/credentials/v1', - 'https://w3id.org/citizenship/v1', - 'https://w3id.org/security/bbs/v1', - ], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['VerifiableCredential', 'PermanentResidentCard'], - issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - issuanceDate: '2019-12-03T12:19:52Z', - expirationDate: '2029-12-03T12:19:52Z', - identifier: '83627465', - name: 'Permanent Resident Card', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['PermanentResident', 'Person'], - givenName: 'JOHN', - familyName: 'SMITH', - gender: 'Male', - image: 'data:image/png;base64,iVBORw0KGgokJggg==', - residentSince: '2015-01-01', - description: 'Government of Example Permanent Resident Card.', - lprCategory: 'C09', - lprNumber: '999-999-999', - commuterClassification: 'C1', - birthCountry: 'Bahamas', - birthDate: '1958-07-17', + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, + }) + + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnectionId) + expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') + expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) + expect(offerCredentialExchangeRecord.threadId).not.toBeNull() + + testLogger.test('Faber waits for credential request from Alice') + await waitForCredentialRecordSubject(faberReplay, { + threadId: aliceCredentialRecord.threadId, + state: CredentialState.RequestReceived, + }) + + testLogger.test('Faber sends credential to Alice') + + await faberAgent.credentials.acceptRequest({ + credentialRecordId: faberCredentialRecord.id, + comment: 'V2 Indy Credential', + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Alice sends credential ack to Faber') + await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + expect(aliceCredentialRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + threadId: expect.any(String), + connectionId: expect.any(String), + state: CredentialState.CredentialReceived, + }) + + const credentialMessage = await faberAgent.credentials.findCredentialMessage(faberCredentialRecord.id) + const w3cCredential = credentialMessage?.credentialAttachments[1].getDataAsJson() + expect(w3cCredential).toMatchObject({ + todo: 'todo', + context: [ + 'https://www.w3.org/2018/credentials/v1', + 'https://w3id.org/citizenship/v1', + 'https://w3id.org/security/bbs/v1', + ], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + description: 'Government of Example Permanent Resident Card.', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + proof: { + type: 'Ed25519Signature2018', + created: expect.any(String), + verificationMethod: + 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', + proofPurpose: 'assertionMethod', + }, + }) + + expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ + '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', + '@id': expect.any(String), + comment: 'V2 Indy Credential', + formats: [ + { + attach_id: expect.any(String), + format: 'hlindy/cred@v2.0', }, - proof: { - type: 'Ed25519Signature2018', - created: expect.any(String), - verificationMethod: - 'did:key:z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL#z6Mkgg342Ycpuk263R9d8Aq6MUaxPn1DDeHyGo38EefXmgDL', - proofPurpose: 'assertionMethod', + { + attach_id: expect.any(String), + format: 'aries/ld-proof-vc@1.0', }, - }) - - expect(JsonTransformer.toJSON(credentialMessage)).toMatchObject({ - '@type': 'https://didcomm.org/issue-credential/2.0/issue-credential', - '@id': expect.any(String), - comment: 'V2 Indy Credential', - formats: [ - { - attach_id: expect.any(String), - format: 'hlindy/cred@v2.0', - }, - { - attach_id: expect.any(String), - format: 'aries/ld-proof-vc@1.0', - }, - ], - 'credentials~attach': [ - { - '@id': expect.any(String), - 'mime-type': 'application/json', - data: expect.any(Object), - lastmod_time: undefined, - byte_count: undefined, - }, - { - '@id': expect.any(String), - 'mime-type': 'application/json', - data: expect.any(Object), - lastmod_time: undefined, - byte_count: undefined, - }, - ], - '~thread': { - thid: expect.any(String), - pthid: undefined, - sender_order: undefined, - received_orders: undefined, + ], + 'credentials~attach': [ + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, }, - '~please_ack': { on: ['RECEIPT'] }, - '~service': undefined, - '~attach': undefined, - '~timing': undefined, - '~transport': undefined, - '~l10n': undefined, - }) - } + { + '@id': expect.any(String), + 'mime-type': 'application/json', + data: expect.any(Object), + lastmod_time: undefined, + byte_count: undefined, + }, + ], + '~thread': { + thid: expect.any(String), + pthid: undefined, + sender_order: undefined, + received_orders: undefined, + }, + '~please_ack': { on: ['RECEIPT'] }, + '~service': undefined, + '~attach': undefined, + '~timing': undefined, + '~transport': undefined, + '~l10n': undefined, + }) }) }) diff --git a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts index 53e5ed3203..797a7f8615 100644 --- a/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts +++ b/packages/core/src/modules/dids/__tests__/DidsModuleConfig.test.ts @@ -1,27 +1,14 @@ import type { DidRegistrar, DidResolver } from '../domain' -import { - KeyDidRegistrar, - IndySdkSovDidRegistrar, - PeerDidRegistrar, - KeyDidResolver, - PeerDidResolver, - IndySdkSovDidResolver, - WebDidResolver, -} from '..' +import { KeyDidRegistrar, PeerDidRegistrar, KeyDidResolver, PeerDidResolver, WebDidResolver } from '..' import { DidsModuleConfig } from '../DidsModuleConfig' describe('DidsModuleConfig', () => { test('sets default values', () => { const config = new DidsModuleConfig() - expect(config.registrars).toEqual([ - expect.any(KeyDidRegistrar), - expect.any(IndySdkSovDidRegistrar), - expect.any(PeerDidRegistrar), - ]) + expect(config.registrars).toEqual([expect.any(KeyDidRegistrar), expect.any(PeerDidRegistrar)]) expect(config.resolvers).toEqual([ - expect.any(IndySdkSovDidResolver), expect.any(WebDidResolver), expect.any(KeyDidResolver), expect.any(PeerDidResolver), diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 5467b601c9..406c3b6d74 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -1,15 +1,17 @@ import type { AgentContext } from '../../../agent' +import type { Wallet } from '../../../wallet' +import indySdk from 'indy-sdk' import { Subject } from 'rxjs' +import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' import { Key, KeyType } from '../../../crypto' import { SigningProviderRegistry } from '../../../crypto/signing-provider' -import { IndyStorageService } from '../../../storage/IndyStorageService' import { JsonTransformer } from '../../../utils' -import { IndyWallet } from '../../../wallet/IndyWallet' import { DidsModuleConfig } from '../DidsModuleConfig' import { DidCommV1Service, DidDocument, DidDocumentBuilder } from '../domain' import { DidDocumentRole } from '../domain/DidDocumentRole' @@ -29,13 +31,13 @@ describe('peer dids', () => { let didRepository: DidRepository let didResolverService: DidResolverService - let wallet: IndyWallet + let wallet: Wallet let agentContext: AgentContext let eventEmitter: EventEmitter beforeEach(async () => { - wallet = new IndyWallet(config.agentDependencies, config.logger, new SigningProviderRegistry([])) - const storageService = new IndyStorageService(config.agentDependencies) + wallet = new IndySdkWallet(indySdk, config.logger, new SigningProviderRegistry([])) + const storageService = new InMemoryStorageService() eventEmitter = new EventEmitter(config.agentDependencies, new Subject()) didRepository = new DidRepository(storageService, eventEmitter) @@ -46,8 +48,7 @@ describe('peer dids', () => { [InjectionSymbols.StorageService, storageService], ], }) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(config.walletConfig!) + await wallet.createAndOpen(config.walletConfig) didResolverService = new DidResolverService( config.logger, diff --git a/packages/core/src/modules/proofs/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index 46a0b584dd..f6e6689936 100644 --- a/packages/core/src/modules/proofs/ProofsModule.ts +++ b/packages/core/src/modules/proofs/ProofsModule.ts @@ -1,5 +1,5 @@ import type { ProofsModuleConfigOptions } from './ProofsModuleConfig' -import type { V2ProofProtocol, V1ProofProtocol } from './protocol' +import type { V2ProofProtocol } from './protocol' import type { ProofProtocol } from './protocol/ProofProtocol' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { ApiModule, DependencyManager } from '../../plugins' @@ -13,7 +13,7 @@ import { ProofRepository } from './repository' /** * Default proofProtocols that will be registered if the `proofProtocols` property is not configured. */ -export type DefaultProofProtocols = [V1ProofProtocol, V2ProofProtocol<[]>] +export type DefaultProofProtocols = [V2ProofProtocol<[]>] // ProofsModuleOptions makes the proofProtocols property optional from the config, as it will set it when not provided. export type ProofsModuleOptions = Optional< diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts index 397e0b8866..7a90b562c8 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts @@ -1,4 +1,5 @@ import type { ProofStateChangedEvent } from '../../../ProofEvents' +import type { ProofFormatService } from '../../../formats' import type { CustomProofTags } from '../../../repository/ProofExchangeRecord' import { Subject } from 'rxjs' @@ -12,7 +13,6 @@ import { uuid } from '../../../../../utils/uuid' import { ConnectionService, DidExchangeState } from '../../../../connections' import { ProofEventTypes } from '../../../ProofEvents' import { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' -import { IndyProofFormatService } from '../../../formats/indy/IndyProofFormatService' import { ProofFormatSpec } from '../../../models/ProofFormatSpec' import { ProofState } from '../../../models/ProofState' import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' @@ -29,12 +29,11 @@ jest.mock('../../../../../storage/Repository') const ProofRepositoryMock = ProofRepository as jest.Mock const connectionServiceMock = ConnectionService as jest.Mock const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const IndyProofFormatServiceMock = IndyProofFormatService as jest.Mock const proofRepository = new ProofRepositoryMock() const connectionService = new connectionServiceMock() const didCommMessageRepository = new didCommMessageRepositoryMock() -const indyProofFormatService = new IndyProofFormatServiceMock() +const proofFormatService = {} satisfies Partial as ProofFormatService const agentConfig = getAgentConfig('V2ProofProtocolTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -49,7 +48,7 @@ const agentContext = getAgentContext({ agentConfig, }) -const proofProtocol = new V2ProofProtocol({ proofFormats: [indyProofFormatService] }) +const proofProtocol = new V2ProofProtocol({ proofFormats: [proofFormatService] }) const connection = getMockConnection({ id: '123', diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts index fbec2351df..62f6c95194 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -1,31 +1,34 @@ import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import type { ProofStateChangedEvent } from '../../../ProofEvents' -import { Subject, ReplaySubject } from 'rxjs' +import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' +import { V1CredentialPreview } from '../../../../../../../anoncreds/src' +import { + getLegacyAnonCredsModules, + issueLegacyAnonCredsCredential, + prepareForAnonCredsIssuance, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' import { - setupProofsTest, waitForProofExchangeRecordSubject, getAgentOptions, - prepareForIndyIssuance, makeConnection, - issueLegacyAnonCredsCredential, -} from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' + testLogger, + setupEventReplaySubjects, +} from '../../../../../../tests' import { Agent } from '../../../../../agent/Agent' import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' import { LinkedAttachment } from '../../../../../utils/LinkedAttachment' import { uuid } from '../../../../../utils/uuid' import { HandshakeProtocol } from '../../../../connections' -import { V1CredentialPreview } from '../../../../credentials' +import { CredentialEventTypes } from '../../../../credentials' import { MediatorPickupStrategy } from '../../../../routing' import { ProofEventTypes } from '../../../ProofEvents' -import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' import { AutoAcceptProof, ProofState } from '../../../models' -describe('Present Proof', () => { +describe('V2 Connectionless Proofs - Indy', () => { let agents: Agent[] afterEach(async () => { @@ -36,42 +39,44 @@ describe('Present Proof', () => { }) test('Faber starts with connection-less proof requests to Alice', async () => { - const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs v2', - 'Alice connection-less Proofs v2', - AutoAcceptProof.Never - ) - agents = [aliceAgent, faberAgent] - testLogger.test('Faber sends presentation request to Alice') - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } + const { + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Proofs v2', + holderName: 'Alice connection-less Proofs v2', + autoAcceptProofs: AutoAcceptProof.Never, + attributeNames: ['name', 'age'], + }) - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, ], - }), - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.RequestReceived, + }, }) + agents = [aliceAgent, faberAgent] + testLogger.test('Faber sends presentation request to Alice') + // eslint-disable-next-line prefer-const let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ protocolVersion: 'v2', @@ -79,8 +84,28 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -94,84 +119,78 @@ describe('Present Proof', () => { await aliceAgent.receiveMessage(requestMessage.toJSON()) testLogger.test('Alice waits for presentation request from Faber') - let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + let aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.RequestReceived, + }) testLogger.test('Alice accepts presentation request from Faber') - const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, }) - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.PresentationReceived, - }) - await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) // assert presentation is valid expect(faberProofExchangeRecord.isVerified).toBe(true) - aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.Done, - }) - // Faber accepts presentation await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits till it receives presentation ack - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.Done, + }) }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { testLogger.test('Faber sends presentation request to Alice') - const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs - Auto Accept', - 'Alice connection-less Proofs - Auto Accept', - AutoAcceptProof.Always - ) - - agents = [aliceAgent, faberAgent] - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } + const { + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Proofs v2 - Auto Accept', + holderName: 'Alice connection-less Proofs v2 - Auto Accept', + autoAcceptProofs: AutoAcceptProof.Always, + attributeNames: ['name', 'age'], + }) - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, ], - }), - } - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.Done, + }, }) - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - state: ProofState.Done, - }) + agents = [aliceAgent, faberAgent] // eslint-disable-next-line prefer-const let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ @@ -180,8 +199,28 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, autoAcceptProof: AutoAcceptProof.ContentApproved, @@ -194,9 +233,13 @@ describe('Present Proof', () => { }) await aliceAgent.receiveMessage(requestMessage.toJSON()) - await aliceProofExchangeRecordPromise + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + }) - await faberProofExchangeRecordPromise + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + }) }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { @@ -214,13 +257,8 @@ describe('Present Proof', () => { endpoints: ['rxjs:mediator'], }) - const faberMessages = new Subject() - const aliceMessages = new Subject() const mediatorMessages = new Subject() - - const subjectMap = { - 'rxjs:mediator': mediatorMessages, - } + const subjectMap = { 'rxjs:mediator': mediatorMessages } // Initialize mediator const mediatorAgent = new Agent(mediatorOptions) @@ -238,47 +276,62 @@ describe('Present Proof', () => { handshakeProtocols: [HandshakeProtocol.Connections], }) - const faberOptions = getAgentOptions(`Connectionless proofs with mediator Faber-${unique}`, { + const anonCredsModules = getLegacyAnonCredsModules({ autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }) - const aliceOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) + const faberOptions = getAgentOptions( + `Connectionless proofs with mediator Faber-${unique}`, + { + mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + anonCredsModules + ) + + const aliceOptions = getAgentOptions( + `Connectionless proofs with mediator Alice-${unique}`, + { + mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + anonCredsModules + ) const faberAgent = new Agent(faberOptions) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) await faberAgent.initialize() const aliceAgent = new Agent(aliceOptions) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) await aliceAgent.initialize() + const [faberReplay, aliceReplay] = setupEventReplaySubjects( + [faberAgent, aliceAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) agents = [aliceAgent, faberAgent, mediatorAgent] - const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age', 'image_0', 'image_1'], + issuerId: faberAgent.publicDid?.did as string, + }) - const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) - expect(faberConnection.isReady).toBe(true) - expect(aliceConnection.isReady).toBe(true) + const [faberConnection] = await makeConnection(faberAgent, aliceAgent) // issue credential with two linked attachments await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, - issuerConnectionId: faberConnection.id, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnection.id, holderAgent: aliceAgent, - credentialTemplate: { - credentialDefinitionId: definition.id, + holderReplay: aliceReplay, + offer: { + credentialDefinitionId: credentialDefinition.credentialDefinitionId, attributes: credentialPreview.attributes, linkedAttachments: [ new LinkedAttachment({ @@ -298,43 +351,6 @@ describe('Present Proof', () => { ], }, }) - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(faberReplay) - aliceAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay) - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ], - }), - } - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.Done, - }) - - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - state: ProofState.Done, - }) // eslint-disable-next-line prefer-const let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ @@ -343,8 +359,28 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, + }, }, }, autoAcceptProof: AutoAcceptProof.ContentApproved, @@ -357,9 +393,7 @@ describe('Present Proof', () => { }) const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() - if (!mediationRecord) { - throw new Error('Faber agent has no default mediator') - } + if (!mediationRecord) throw new Error('Faber agent has no default mediator') expect(requestMessage).toMatchObject({ service: { @@ -371,8 +405,12 @@ describe('Present Proof', () => { await aliceAgent.receiveMessage(requestMessage.toJSON()) - await aliceProofExchangeRecordPromise + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + }) - await faberProofExchangeRecordPromise + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + }) }) }) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts index 72c1ee44ef..3aa53817df 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts @@ -1,41 +1,62 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' +import type { V2ProposePresentationMessage, V2RequestPresentationMessage } from '../messages' import { AnonCredsProofRequest } from '../../../../../../../anoncreds/src/models/AnonCredsProofRequest' -import { setupAnonCredsTests } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' +import { + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecordSubject, testLogger } from '../../../../../../tests' import { JsonTransformer } from '../../../../../utils/JsonTransformer' -import { AttributeFilter } from '../../../formats/indy/models/AttributeFilter' -import { PredicateType } from '../../../formats/indy/models/PredicateType' -import { ProofAttributeInfo } from '../../../formats/indy/models/ProofAttributeInfo' -import { ProofPredicateInfo } from '../../../formats/indy/models/ProofPredicateInfo' -import { ProofRequest } from '../../../formats/indy/models/ProofRequest' import { ProofState } from '../../../models/ProofState' -import { V2ProposePresentationMessage, V2RequestPresentationMessage } from '../messages' -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository +describe('V2 Proofs Negotiation - Indy', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, aliceConnection, presentationPreview } = await setupAnonCredsTests({ - issuerName: 'Faber agent', - holderName: 'Alice agent', - attributeNames: ['name', 'image_0'], + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + + credentialDefinitionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent v2', + holderName: 'Alice agent v2', + attributeNames: ['name', 'age'], })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) }) afterAll(async () => { @@ -49,34 +70,33 @@ describe('Present Proof', () => { test(`Proof negotiation between Alice and Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v2', proofFormats: { indy: { name: 'proof-request', version: '1.0', - attributes: presentationPreview.attributes.filter((attribute) => attribute.name !== 'name'), - predicates: presentationPreview.predicates, + attributes: [], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], }, }, comment: 'V2 propose proof test 1', }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - let proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposePresentationMessage, + let faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.ProposalReceived, }) + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ @@ -97,29 +117,28 @@ describe('Present Proof', () => { id: expect.any(String), comment: 'V2 propose proof test 1', }) + // eslint-disable-next-line @typescript-eslint/no-explicit-any - let proposalAttach = proposal?.proposalAttachments[0].getDataAsJson() as any - let attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] - let predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] + const proposalAttach = (proposal as V2ProposePresentationMessage)?.proposalAttachments?.[0].getDataAsJson() expect(proposalAttach).toMatchObject({ requested_attributes: { - [attributesGroup]: { + something: { name: 'image_0', restrictions: [ { - cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [predicatesGroup]: { + something_else: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -132,44 +151,6 @@ describe('Present Proof', () => { protocolVersion: 'v2', }) - // Negotiate Proposal - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, - }) - testLogger.test('Faber sends new proof request to Alice') faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal({ proofRecordId: faberProofExchangeRecord.id, @@ -177,22 +158,39 @@ describe('Present Proof', () => { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - let request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, }) + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', id: expect.any(String), @@ -218,34 +216,33 @@ describe('Present Proof', () => { testLogger.test('Alice sends proof proposal to Faber') - faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - aliceProofExchangeRecord = await aliceAgent.proofs.negotiateRequest({ proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: { name: 'proof-request', version: '1.0', - attributes: presentationPreview.attributes.filter((attribute) => attribute.name === 'name'), - predicates: presentationPreview.predicates, + attributes: [], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], }, }, comment: 'V2 propose proof test 2', }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposePresentationMessage, + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.ProposalReceived, }) - expect(proposal).toMatchObject({ + const proposal2 = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) + expect(proposal2).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ { @@ -265,29 +262,18 @@ describe('Present Proof', () => { id: expect.any(String), comment: 'V2 propose proof test 2', }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - proposalAttach = proposal?.proposalAttachments[0].getDataAsJson() as any - attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] - predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] - expect(proposalAttach).toMatchObject({ - requested_attributes: { - [attributesGroup]: { - name: 'name', - restrictions: [ - { - cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - }, - }, + + const proposalAttach2 = (proposal as V2ProposePresentationMessage)?.proposalAttachments[0].getDataAsJson() + expect(proposalAttach2).toMatchObject({ + requested_attributes: {}, requested_predicates: { - [predicatesGroup]: { + something_else: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -301,29 +287,19 @@ describe('Present Proof', () => { }) // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id, - } - - aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, }) - testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, }) - expect(request).toMatchObject({ + const request2 = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) + expect(request2).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ { @@ -353,7 +329,6 @@ describe('Present Proof', () => { }) const proposalMessage = await aliceAgent.proofs.findProposalMessage(aliceProofExchangeRecord.id) - expect(proposalMessage).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ @@ -375,29 +350,26 @@ describe('Present Proof', () => { comment: 'V2 propose proof test 2', }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - proposalAttach = proposal?.proposalAttachments[0].getDataAsJson() as any - attributesGroup = Object.keys(proposalAttach.requested_attributes ?? {})[0] - predicatesGroup = Object.keys(proposalAttach.requested_predicates ?? {})[0] - expect(proposalAttach).toMatchObject({ + const proposalAttach3 = (proposal as V2ProposePresentationMessage)?.proposalAttachments[0].getDataAsJson() + expect(proposalAttach3).toMatchObject({ requested_attributes: { - [attributesGroup]: { + something: { name: 'name', restrictions: [ { - cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [predicatesGroup]: { + somethingElse: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -423,7 +395,7 @@ describe('Present Proof', () => { name: 'name', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -435,7 +407,7 @@ describe('Present Proof', () => { p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts index 2fccef1f98..ee4d3cd44b 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-presentation.e2e.test.ts @@ -1,31 +1,60 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' -import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' + +import { + setupAnonCredsTests, + issueLegacyAnonCredsCredential, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecordSubject, testLogger } from '../../../../../../tests' import { ProofState } from '../../../models/ProofState' import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import { V2PresentationMessage, V2RequestPresentationMessage } from '../messages' -import { V2ProposePresentationMessage } from '../messages/V2ProposePresentationMessage' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository + +describe('V2 Proofs - Indy', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent v2 present proof', - 'Alice agent v2 present proof' - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + + credentialDefinitionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent v2', + holderName: 'Alice agent v2', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) }) afterAll(async () => { @@ -39,34 +68,39 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v2', proofFormats: { indy: { name: 'ProofRequest', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'Alice', + credentialDefinitionId, + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], }, }, comment: 'V2 propose proof test', }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberPresentationRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposePresentationMessage, + let faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.ProposalReceived, }) + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ @@ -93,32 +127,20 @@ describe('Present Proof', () => { state: ProofState.ProposalReceived, protocolVersion: 'v2', }) - }) - test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id, - } - - const alicePresentationRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, }) - testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await alicePresentationRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, }) + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ @@ -147,9 +169,7 @@ describe('Present Proof', () => { state: ProofState.RequestReceived, protocolVersion: 'v2', }) - }) - test(`Alice accepts presentation request from Faber`, async () => { // Alice retrieves the requested credentials and accepts the presentation request testLogger.test('Alice accepts presentation request from Faber') @@ -157,25 +177,20 @@ describe('Present Proof', () => { proofRecordId: aliceProofExchangeRecord.id, }) - const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.PresentationReceived, - }) - await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) + // Faber waits for the presentation from Alice testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberPresentationRecordPromise - - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2PresentationMessage, - }) + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/presentation', formats: [ @@ -204,10 +219,8 @@ describe('Present Proof', () => { state: ProofState.PresentationReceived, protocolVersion: 'v2', }) - }) - test(`Faber accepts the presentation provided by Alice`, async () => { - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-proposal.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-proposal.e2e.test.ts deleted file mode 100644 index 68c09d5717..0000000000 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-proposal.e2e.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProofExchangeRecord } from '../../../repository' -import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' -import { ProofState } from '../../../models/ProofState' -import { V2ProposePresentationMessage } from '../messages/V2ProposePresentationMessage' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberPresentationRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent v2', - 'Alice agent v2' - )) - }) - - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - test(`Alice Creates and sends Proof Proposal to Faber`, async () => { - testLogger.test('Alice sends proof proposal to Faber') - - const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v2', - proofFormats: { - indy: { - name: 'ProofRequest', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - comment: 'V2 propose proof test', - }) - - testLogger.test('Faber waits for presentation from Alice') - faberPresentationRecord = await faberPresentationRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberPresentationRecord.id, - messageClass: V2ProposePresentationMessage, - }) - - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/2.0/propose-presentation', - formats: [ - { - attachmentId: expect.any(String), - format: 'hlindy/proof-req@v2.0', - }, - ], - proposalAttachments: [ - { - id: expect.any(String), - mimeType: 'application/json', - data: { - base64: expect.any(String), - }, - }, - ], - id: expect.any(String), - comment: 'V2 propose proof test', - }) - expect(faberPresentationRecord).toMatchObject({ - id: expect.anything(), - threadId: faberPresentationRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v2', - }) - }) -}) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts index 827555ec65..afe41e9417 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-request.e2e.test.ts @@ -1,31 +1,60 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions } from '../../../ProofsApiOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V1PresentationPreview } from '../../v1/models/V1PresentationPreview' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' + +import { + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecordSubject } from '../../../../../../tests' import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' import { ProofState } from '../../../models/ProofState' -import { V2RequestPresentationMessage } from '../messages' -import { V2ProposePresentationMessage } from '../messages/V2ProposePresentationMessage' -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository +describe('V2 Proofs - Indy', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent v2', - 'Alice agent v2' - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + + credentialDefinitionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber agent v2', + holderName: 'Alice agent v2', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) }) afterAll(async () => { @@ -39,34 +68,39 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberPresentationRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v2', proofFormats: { indy: { name: 'ProofRequest', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'Alice', + credentialDefinitionId, + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], }, }, comment: 'V2 propose proof test', }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberPresentationRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2ProposePresentationMessage, + let faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.ProposalReceived, }) + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ @@ -93,32 +127,20 @@ describe('Present Proof', () => { state: ProofState.ProposalReceived, protocolVersion: 'v2', }) - }) - test(`Faber accepts the Proposal sent by Alice`, async () => { // Accept Proposal - const acceptProposalOptions: AcceptProofProposalOptions = { + testLogger.test('Faber accepts presentation proposal from Alice') + faberProofExchangeRecord = await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id, - } - - const alicePresentationRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, }) - testLogger.test('Faber accepts presentation proposal from Alice') - faberProofExchangeRecord = await faberAgent.proofs.acceptProposal(acceptProposalOptions) - testLogger.test('Alice waits for proof request from Faber') - aliceProofExchangeRecord = await alicePresentationRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V2RequestPresentationMessage, + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, }) + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/request-presentation', formats: [ diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts index e3a9841613..bc33293db5 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts @@ -1,29 +1,59 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { V1PresentationPreview } from '../../v1' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' +import { + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecord, testLogger } from '../../../../../../tests' import { AutoAcceptProof, ProofState } from '../../../models' describe('Auto accept present proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - - describe('Auto accept on `always`', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let credentialDefinitionId: string + let faberConnectionId: string + let aliceConnectionId: string + + describe("Auto accept on 'always'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Always Proofs', - 'Alice Auto Accept Always Proofs', - AutoAcceptProof.Always - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept Always Proofs', + holderName: 'Alice Auto Accept Always Proofs', + attributeNames: ['name', 'age'], + autoAcceptProofs: AutoAcceptProof.Always, + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) }) + afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -31,18 +61,31 @@ describe('Auto accept present proof', () => { await aliceAgent.wallet.delete() }) - test('Alice starts with proof proposal to Faber, both with autoAcceptProof on `always`', async () => { + test("Alice starts with proof proposal to Faber, both with autoAcceptProof on 'always'", async () => { testLogger.test('Alice sends presentation proposal to Faber') await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', proofFormats: { indy: { name: 'abc', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + credentialDefinitionId, + name: 'name', + value: 'Alice', + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 50, + }, + ], }, }, }) @@ -55,40 +98,38 @@ describe('Auto accept present proof', () => { ]) }) - test('Faber starts with proof requests to Alice, both with autoAcceptProof on `always`', async () => { + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'always'", async () => { testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } await faberAgent.proofs.requestProof({ protocolVersion: 'v2', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -105,13 +146,41 @@ describe('Auto accept present proof', () => { describe("Auto accept on 'contentApproved'", () => { beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Content Approved Proofs', - 'Alice Auto Accept Content Approved Proofs', - AutoAcceptProof.ContentApproved - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept ContentApproved Proofs', + holderName: 'Alice Auto Accept ContentApproved Proofs', + attributeNames: ['name', 'age'], + autoAcceptProofs: AutoAcceptProof.ContentApproved, + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'Alice', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) }) + afterAll(async () => { testLogger.test('Shutting down both agents') await faberAgent.shutdown() @@ -128,14 +197,27 @@ describe('Auto accept present proof', () => { }) await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', proofFormats: { indy: { - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, name: 'abc', version: '1.0', + attributes: [ + { + credentialDefinitionId, + name: 'name', + value: 'Alice', + }, + ], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 50, + }, + ], }, }, }) @@ -153,28 +235,6 @@ describe('Auto accept present proof', () => { test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'contentApproved'", async () => { testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, @@ -182,13 +242,33 @@ describe('Auto accept present proof', () => { await faberAgent.proofs.requestProof({ protocolVersion: 'v2', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requestedAttributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requestedPredicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts index 91c389b4d1..32cbc38c4a 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts @@ -1,11 +1,11 @@ import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' -import type { ReplaySubject } from 'rxjs' +import type { EventReplaySubject } from '../../../../../../tests' import { issueLegacyAnonCredsCredential, setupAnonCredsTests, } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' -import { waitForProofExchangeRecord } from '../../../../../../tests/helpers' +import { waitForProofExchangeRecord } from '../../../../../../tests' import testLogger from '../../../../../../tests/logger' import { ProofState } from '../../../models' import { ProofExchangeRecord } from '../../../repository' @@ -13,9 +13,9 @@ import { V2ProposePresentationMessage, V2RequestPresentationMessage, V2Presentat describe('Present Proof', () => { let faberAgent: AnonCredsTestsAgent - let faberReplay: ReplaySubject + let faberReplay: EventReplaySubject let aliceAgent: AnonCredsTestsAgent - let aliceReplay: ReplaySubject + let aliceReplay: EventReplaySubject let credentialDefinitionId: string let aliceConnectionId: string let faberConnectionId: string diff --git a/packages/core/tests/events.ts b/packages/core/tests/events.ts new file mode 100644 index 0000000000..e48f689f1e --- /dev/null +++ b/packages/core/tests/events.ts @@ -0,0 +1,21 @@ +import type { Agent, BaseEvent } from '../src' + +import { ReplaySubject } from 'rxjs' + +export type EventReplaySubject = ReplaySubject + +export function setupEventReplaySubjects(agents: Agent[], eventTypes: string[]): ReplaySubject[] { + const replaySubjects: EventReplaySubject[] = [] + + for (const agent of agents) { + const replaySubject = new ReplaySubject() + + for (const eventType of eventTypes) { + agent.events.observable(eventType).subscribe(replaySubject) + } + + replaySubjects.push(replaySubject) + } + + return replaySubjects +} diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 4c268d07ed..752cd14f85 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -61,8 +61,6 @@ export const taaVersion = (process.env.TEST_AGENT_TAA_VERSION ?? '1') as `${numb export const taaAcceptanceMechanism = process.env.TEST_AGENT_TAA_ACCEPTANCE_MECHANISM ?? 'accept' export { agentDependencies } -export type EventReplaySubject = ReplaySubject - export function getAgentOptions( name: string, extraConfig: Partial = {}, diff --git a/packages/core/tests/index.ts b/packages/core/tests/index.ts new file mode 100644 index 0000000000..b8ea2ca430 --- /dev/null +++ b/packages/core/tests/index.ts @@ -0,0 +1,8 @@ +export * from './jsonld' +export * from './transport' +export * from './events' +export * from './helpers' + +import testLogger from './logger' + +export { testLogger } diff --git a/packages/core/tests/jsonld.ts b/packages/core/tests/jsonld.ts new file mode 100644 index 0000000000..c1112d76e3 --- /dev/null +++ b/packages/core/tests/jsonld.ts @@ -0,0 +1,162 @@ +import type { EventReplaySubject } from './events' +import type { AutoAcceptCredential, AutoAcceptProof, ConnectionRecord } from '../src' + +import { + CacheModule, + CredentialEventTypes, + InMemoryLruCache, + ProofEventTypes, + Agent, + ProofsModule, + CredentialsModule, + JsonLdCredentialFormatService, + V2CredentialProtocol, + W3cVcModule, +} from '../src' +import { customDocumentLoader } from '../src/modules/vc/__tests__/documentLoader' + +import { setupEventReplaySubjects } from './events' +import { getAgentOptions, makeConnection } from './helpers' +import { setupSubjectTransports } from './transport' + +export type JsonLdTestsAgent = Agent> + +export const getJsonLdModules = ({ + autoAcceptCredentials, + autoAcceptProofs, +}: { autoAcceptCredentials?: AutoAcceptCredential; autoAcceptProofs?: AutoAcceptProof } = {}) => + ({ + credentials: new CredentialsModule({ + credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], + autoAcceptCredentials, + }), + w3cVc: new W3cVcModule({ + documentLoader: customDocumentLoader, + }), + proofs: new ProofsModule({ + autoAcceptProofs, + }), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), + } as const) + +interface SetupJsonLdTestsReturn { + issuerAgent: JsonLdTestsAgent + issuerReplay: EventReplaySubject + + holderAgent: JsonLdTestsAgent + holderReplay: EventReplaySubject + + issuerHolderConnectionId: CreateConnections extends true ? string : undefined + holderIssuerConnectionId: CreateConnections extends true ? string : undefined + + verifierHolderConnectionId: CreateConnections extends true + ? VerifierName extends string + ? string + : undefined + : undefined + holderVerifierConnectionId: CreateConnections extends true + ? VerifierName extends string + ? string + : undefined + : undefined + + verifierAgent: VerifierName extends string ? JsonLdTestsAgent : undefined + verifierReplay: VerifierName extends string ? EventReplaySubject : undefined + + credentialDefinitionId: string +} + +export async function setupJsonLdTests< + VerifierName extends string | undefined = undefined, + CreateConnections extends boolean = true +>({ + issuerName, + holderName, + verifierName, + autoAcceptCredentials, + autoAcceptProofs, + createConnections, +}: { + issuerName: string + holderName: string + verifierName?: VerifierName + autoAcceptCredentials?: AutoAcceptCredential + autoAcceptProofs?: AutoAcceptProof + createConnections?: CreateConnections +}): Promise> { + const modules = getJsonLdModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + + const issuerAgent = new Agent( + getAgentOptions( + issuerName, + { + endpoints: ['rxjs:issuer'], + }, + modules + ) + ) + + const holderAgent = new Agent( + getAgentOptions( + holderName, + { + endpoints: ['rxjs:holder'], + }, + modules + ) + ) + + const verifierAgent = new Agent( + getAgentOptions( + verifierName ?? 'NOT USED -- NOT INITIALIZED', + { + endpoints: ['rxjs:verifier'], + }, + modules + ) + ) + + setupSubjectTransports([issuerAgent, holderAgent, verifierAgent]) + const [issuerReplay, holderReplay, verifierReplay] = setupEventReplaySubjects( + [issuerAgent, holderAgent, verifierAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + + await issuerAgent.initialize() + await holderAgent.initialize() + if (verifierName) await verifierAgent.initialize() + + let issuerHolderConnection: ConnectionRecord | undefined + let holderIssuerConnection: ConnectionRecord | undefined + let verifierHolderConnection: ConnectionRecord | undefined + let holderVerifierConnection: ConnectionRecord | undefined + + if (createConnections ?? true) { + ;[issuerHolderConnection, holderIssuerConnection] = await makeConnection(issuerAgent, holderAgent) + + if (verifierName) { + ;[holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) + } + } + + return { + issuerAgent, + issuerReplay, + + holderAgent, + holderReplay, + + verifierAgent: verifierName ? verifierAgent : undefined, + verifierReplay: verifierName ? verifierReplay : undefined, + + issuerHolderConnectionId: issuerHolderConnection?.id, + holderIssuerConnectionId: holderIssuerConnection?.id, + holderVerifierConnectionId: holderVerifierConnection?.id, + verifierHolderConnectionId: verifierHolderConnection?.id, + } as unknown as SetupJsonLdTestsReturn +} diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index fa69bd7de6..1a0311e656 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -1,4 +1,4 @@ -import type { EventReplaySubject } from './helpers' +import type { EventReplaySubject } from './events' import type { AnonCredsTestsAgent } from '../../anoncreds/tests/legacyAnonCredsSetup' import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../anoncreds/tests/legacyAnonCredsSetup' diff --git a/packages/core/tests/transport.ts b/packages/core/tests/transport.ts new file mode 100644 index 0000000000..2577fdd428 --- /dev/null +++ b/packages/core/tests/transport.ts @@ -0,0 +1,18 @@ +import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' +import type { Agent } from '../src' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' + +export function setupSubjectTransports(agents: Agent[]) { + const subjectMap: Record> = {} + + for (const agent of agents) { + const messages = new Subject() + subjectMap[agent.config.endpoints[0]] = messages + agent.registerInboundTransport(new SubjectInboundTransport(messages)) + agent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + } +} From 5d716e30c6c625a198b91ae0abeb0813e1b4d4a2 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 14 Feb 2023 23:45:08 +0100 Subject: [PATCH 10/22] fix: types fixed Signed-off-by: Timo Glastra --- demo/src/Alice.ts | 2 +- demo/src/BaseAgent.ts | 157 ++++++- demo/src/Faber.ts | 87 ++-- packages/anoncreds-rs/package.json | 4 +- .../V1CredentialProtocolProposeOffer.test.ts | 11 +- .../v1-connectionless-credentials.e2e.test.ts | 83 +--- .../v1-credentials-auto-accept.e2e.test.ts | 140 +++---- .../v1/__tests__/V1ProofProtocol.test.ts | 35 +- .../v1-connectionless-proofs.e2e.test.ts | 385 ++++++++++-------- .../v1-indy-proof-negotiation.test.ts | 195 ++++----- .../v1-indy-proof-presentation.test.e2e.ts | 103 +++-- .../v1-indy-proof-proposal.test.e2e.ts | 72 ++-- .../v1-indy-proof-request.e2e.test.ts | 66 +-- .../v1/__tests__/v1-indy-proofs.e2e.test.ts | 297 +++++++------- .../v1-proofs-auto-accept.e2e.test.ts | 229 +++++++---- .../anoncreds/tests/legacyAnonCredsSetup.ts | 4 +- packages/askar/package.json | 4 +- packages/askar/src/wallet/AskarWallet.ts | 5 +- .../tests/bbs-signatures.e2e.test.ts | 12 +- .../tests/bbs-signing-provider.e2e.test.ts | 32 +- ...proof.credentials.propose-offerBbs.test.ts | 162 ++++---- .../v2-indy-connectionless-proofs.e2e.test.ts | 12 +- .../v2-indy-proof-negotiation.test.ts | 4 +- .../v2-indy-proofs-auto-accept.2e.test.ts | 8 +- .../v2/__tests__/v2-indy-proofs.e2e.test.ts | 12 +- packages/core/tests/jsonld.ts | 8 + .../core/tests/proofs-sub-protocol.test.ts | 8 +- packages/indy-sdk/src/ledger/IndySdkPool.ts | 11 +- .../wallet/__tests__/IndySdkWallet.test.ts | 6 +- .../tests/sov-did-registrar.e2e.test.ts | 7 +- packages/indy-vdr/package.json | 4 +- .../src/anoncreds/IndyVdrAnonCredsRegistry.ts | 6 +- packages/indy-vdr/src/pool/IndyVdrPool.ts | 15 +- .../indy-vdr/src/pool/IndyVdrPoolService.ts | 2 +- .../tests/indy-vdr-did-resolver.e2e.test.ts | 7 +- .../indy-vdr/tests/indy-vdr-pool.e2e.test.ts | 8 +- .../__tests__/QuestionAnswerService.test.ts | 16 +- yarn.lock | 54 +-- 38 files changed, 1220 insertions(+), 1053 deletions(-) diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index aa705ca7a4..9c53abbbc0 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -8,7 +8,7 @@ export class Alice extends BaseAgent { public connectionRecordFaberId?: string public constructor(port: number, name: string) { - super(port, name) + super({ port, name, useSharedComponents: false }) this.connected = false } diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index 1f87aa7942..d5b568297a 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -2,10 +2,20 @@ import type { IndySdkPoolConfig } from '../../packages/indy-sdk/src/ledger' import type { IndyVdrPoolConfig } from '../../packages/indy-vdr/src/pool' import type { InitConfig } from '@aries-framework/core' -import { AnonCredsModule } from '@aries-framework/anoncreds' +import { + AnonCredsModule, + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, + V1CredentialProtocol, + V1ProofProtocol, +} from '@aries-framework/anoncreds' import { AnonCredsRsModule } from '@aries-framework/anoncreds-rs' import { AskarModule } from '@aries-framework/askar' import { + KeyType, + DidsModule, + V2ProofProtocol, + V2CredentialProtocol, ProofsModule, AutoAcceptProof, AutoAcceptCredential, @@ -13,9 +23,11 @@ import { Agent, HttpOutboundTransport, } from '@aries-framework/core' -import { IndySdkAnonCredsRegistry, IndySdkSovDidResolver, IndySdkSovDidRegistrar } from '@aries-framework/indy-sdk' -import { IndyVdrSovDidResolver } from '@aries-framework/indy-vdr' +import { IndySdkAnonCredsRegistry, IndySdkModule, IndySdkSovDidResolver } from '@aries-framework/indy-sdk' +import { IndyVdrAnonCredsRegistry, IndyVdrModule, IndyVdrSovDidResolver } from '@aries-framework/indy-vdr' import { agentDependencies, HttpInboundTransport } from '@aries-framework/node' +import { randomUUID } from 'crypto' +import indySdk from 'indy-sdk' import { greenText } from './OutputClass' @@ -25,18 +37,33 @@ const bcovrin = `{"reqSignature":{},"txn":{"data":{"data":{"alias":"Node1","blsk {"reqSignature":{},"txn":{"data":{"data":{"alias":"Node4","blskey":"2zN3bHM1m4rLz54MJHYSwvqzPchYp8jkHswveCLAEJVcX6Mm1wHQD1SkPYMzUDTZvWvhuE6VNAkK3KxVeEmsanSmvjVkReDeBEMxeDaayjcZjFGPydyey1qxBHmTvAnBKoPydvuTAqx5f7YNNRAdeLmUi99gERUU7TD8KfAa6MpQ9bw","blskey_pop":"RPLagxaR5xdimFzwmzYnz4ZhWtYQEj8iR5ZU53T2gitPCyCHQneUn2Huc4oeLd2B2HzkGnjAff4hWTJT6C7qHYB1Mv2wU5iHHGFWkhnTX9WsEAbunJCV2qcaXScKj4tTfvdDKfLiVuU2av6hbsMztirRze7LvYBkRHV3tGwyCptsrP","client_ip":"138.197.138.255","client_port":9708,"node_ip":"138.197.138.255","node_port":9707,"services":["VALIDATOR"]},"dest":"4PS3EDQ3dW1tci1Bp6543CfuuebjFrg36kLAUcskGfaA"},"metadata":{"from":"TWwCRQRZ2ZHMJFn9TzLp7W"},"type":"0"},"txnMetadata":{"seqNo":4,"txnId":"aa5e817d7cc626170eca175822029339a444eb0ee8f0bd20d3b0b76e566fb008"},"ver":"1"}` const indyNetworkConfig = { + // Need unique network id as we will have multiple agent processes in the agent + id: randomUUID(), genesisTransactions: bcovrin, indyNamespace: 'bcovrin:test', isProduction: false, + connectOnStartup: true, } satisfies IndySdkPoolConfig | IndyVdrPoolConfig +type DemoAgent = Agent | ReturnType> + export class BaseAgent { public port: number public name: string public config: InitConfig - public agent: Agent + public agent: DemoAgent + public anonCredsIssuerId: string + public usesSharedComponents: boolean - public constructor(port: number, name: string) { + public constructor({ + port, + name, + useSharedComponents, + }: { + port: number + name: string + useSharedComponents: boolean + }) { this.name = name this.port = port @@ -46,29 +73,21 @@ export class BaseAgent { id: name, key: name, }, - publicDidSeed: '6b8b882e2618fa5d45ee7229ca880083', + publicDidSeed: 'afjdemoverysercure00000000000000', endpoints: [`http://localhost:${this.port}`], autoAcceptConnections: true, } satisfies InitConfig this.config = config + // TODO: do not hardcode this + this.anonCredsIssuerId = '2jEvRuKmfBJTRa7QowDpNN' + this.usesSharedComponents = useSharedComponents + this.agent = new Agent({ config, dependencies: agentDependencies, - modules: { - credentials: new CredentialsModule({ - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - }), - proofs: new ProofsModule({ - autoAcceptProofs: AutoAcceptProof.ContentApproved, - }), - anoncreds: new AnonCredsModule({ - registries: [new IndySdkAnonCredsRegistry()], - }), - anoncredsRs: new AnonCredsRsModule(), - indyVdr: new IndyVdrModule(), - }, + modules: useSharedComponents ? getSharedComponentModules() : getIndySdkModules(), }) this.agent.registerInboundTransport(new HttpInboundTransport({ port })) this.agent.registerOutboundTransport(new HttpOutboundTransport()) @@ -76,6 +95,106 @@ export class BaseAgent { public async initializeAgent() { await this.agent.initialize() + + // FIXME: + // We need to make sure the key to submit transactions is created. We should update this to use the dids module, and allow + // to add an existing did based on a seed/secretKey, and not register it on the the ledger. However for Indy SDK we currently + // use the deprecated publicDidSeed property (which will register the did in the wallet), and for Askar we manually create the key + // in the wallet. Additional issue is that when creating a key in askar using the seed, it will give different results than indy-sdk + // but if we create the key in askar from the seed, but use it as the secret key, it will work. + // For IndySDK we can't call createKey as it won't allow to sign the transactions in that case, as it needs to be a did + if (this.usesSharedComponents) { + try { + await this.agent.context.wallet.createKey({ + keyType: KeyType.Ed25519, + seed: 'afjdemoverysercure00000000000000', + }) + } catch (error) { + // We assume the key already exists, and that's why askar failed + } + } + console.log(greenText(`\nAgent ${this.name} created!\n`)) } } + +function getSharedComponentModules() { + const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService() + const legacyIndyProofFormatService = new LegacyIndyProofFormatService() + + return { + credentials: new CredentialsModule({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + credentialProtocols: [ + new V1CredentialProtocol({ + indyCredentialFormat: legacyIndyCredentialFormatService, + }), + new V2CredentialProtocol({ + credentialFormats: [legacyIndyCredentialFormatService], + }), + ], + }), + proofs: new ProofsModule({ + autoAcceptProofs: AutoAcceptProof.ContentApproved, + proofProtocols: [ + new V1ProofProtocol({ + indyProofFormat: legacyIndyProofFormatService, + }), + new V2ProofProtocol({ + proofFormats: [legacyIndyProofFormatService], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndyVdrAnonCredsRegistry()], + }), + anoncredsRs: new AnonCredsRsModule(), + indyVdr: new IndyVdrModule({ + networks: [indyNetworkConfig], + }), + dids: new DidsModule({ + resolvers: [new IndyVdrSovDidResolver()], + }), + askar: new AskarModule(), + } as const +} + +function getIndySdkModules() { + const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService() + const legacyIndyProofFormatService = new LegacyIndyProofFormatService() + + return { + credentials: new CredentialsModule({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + credentialProtocols: [ + new V1CredentialProtocol({ + indyCredentialFormat: legacyIndyCredentialFormatService, + }), + new V2CredentialProtocol({ + credentialFormats: [legacyIndyCredentialFormatService], + }), + ], + }), + proofs: new ProofsModule({ + autoAcceptProofs: AutoAcceptProof.ContentApproved, + proofProtocols: [ + new V1ProofProtocol({ + indyProofFormat: legacyIndyProofFormatService, + }), + new V2ProofProtocol({ + proofFormats: [legacyIndyProofFormatService], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndySdkAnonCredsRegistry()], + }), + indySdk: new IndySdkModule({ + indySdk, + networks: [indyNetworkConfig], + }), + dids: new DidsModule({ + resolvers: [new IndySdkSovDidResolver()], + }), + } as const +} diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index a19906d0fa..655714d7d1 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -1,8 +1,8 @@ +import type { RegisterCredentialDefinitionReturnStateFinished } from '../../packages/anoncreds/src' import type { ConnectionRecord, ConnectionStateChangedEvent } from '@aries-framework/core' -import type { CredDef, Schema } from 'indy-sdk' import type BottomBar from 'inquirer/lib/ui/bottom-bar' -import { utils, V1CredentialPreview, ConnectionEventTypes } from '@aries-framework/core' +import { utils, ConnectionEventTypes } from '@aries-framework/core' import { ui } from 'inquirer' import { BaseAgent } from './BaseAgent' @@ -10,11 +10,11 @@ import { Color, greenText, Output, purpleText, redText } from './OutputClass' export class Faber extends BaseAgent { public outOfBandId?: string - public credentialDefinition?: CredDef + public credentialDefinition?: RegisterCredentialDefinitionReturnStateFinished public ui: BottomBar public constructor(port: number, name: string) { - super(port, name) + super({ port, name, useSharedComponents: true }) this.ui = new ui.BottomBar() } @@ -105,39 +105,57 @@ export class Faber extends BaseAgent { const schemaTemplate = { name: 'Faber College' + utils.uuid(), version: '1.0.0', - attributes: ['name', 'degree', 'date'], + attrNames: ['name', 'degree', 'date'], + issuerId: this.anonCredsIssuerId, } - this.printSchema(schemaTemplate.name, schemaTemplate.version, schemaTemplate.attributes) + this.printSchema(schemaTemplate.name, schemaTemplate.version, schemaTemplate.attrNames) this.ui.updateBottomBar(greenText('\nRegistering schema...\n', false)) - const schema = await this.agent.ledger.registerSchema(schemaTemplate) + + const { schemaState } = await this.agent.modules.anoncreds.registerSchema({ + schema: schemaTemplate, + options: { + didIndyNamespace: 'bcovrin:test', + }, + }) + + if (schemaState.state !== 'finished') { + throw new Error( + `Error registering schema: ${schemaState.state === 'failed' ? schemaState.reason : 'Not Finished'}}` + ) + } this.ui.updateBottomBar('\nSchema registered!\n') - return schema + return schemaState } - private async registerCredentialDefinition(schema: Schema) { + private async registerCredentialDefinition(schemaId: string) { this.ui.updateBottomBar('\nRegistering credential definition...\n') - this.credentialDefinition = await this.agent.ledger.registerCredentialDefinition({ - schema, - tag: 'latest', - supportRevocation: false, + const { credentialDefinitionState } = await this.agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition: { + schemaId, + issuerId: this.anonCredsIssuerId, + tag: 'latest', + }, + options: { + didIndyNamespace: 'bcovrin:test', + }, }) + + if (credentialDefinitionState.state !== 'finished') { + throw new Error( + `Error registering credential definition: ${ + credentialDefinitionState.state === 'failed' ? credentialDefinitionState.reason : 'Not Finished' + }}` + ) + } + + this.credentialDefinition = credentialDefinitionState this.ui.updateBottomBar('\nCredential definition registered!!\n') return this.credentialDefinition } - private getCredentialPreview() { - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'Alice Smith', - degree: 'Computer Science', - date: '01/01/2022', - }) - return credentialPreview - } - public async issueCredential() { const schema = await this.registerSchema() - const credDef = await this.registerCredentialDefinition(schema) - const credentialPreview = this.getCredentialPreview() + const credentialDefinition = await this.registerCredentialDefinition(schema.schemaId) const connectionRecord = await this.getConnectionRecord() this.ui.updateBottomBar('\nSending credential offer...\n') @@ -147,8 +165,21 @@ export class Faber extends BaseAgent { protocolVersion: 'v1', credentialFormats: { indy: { - attributes: credentialPreview.attributes, - credentialDefinitionId: credDef.id, + attributes: [ + { + name: 'name', + value: 'Alice Smith', + }, + { + name: 'degree', + value: 'Computer Science', + }, + { + name: 'date', + value: '01/01/2022', + }, + ], + credentialDefinitionId: credentialDefinition.credentialDefinitionId, }, }, }) @@ -169,7 +200,7 @@ export class Faber extends BaseAgent { name: 'name', restrictions: [ { - credentialDefinitionId: this.credentialDefinition?.id, + cred_def_id: this.credentialDefinition?.credentialDefinitionId, }, ], }, @@ -190,7 +221,7 @@ export class Faber extends BaseAgent { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: proofAttribute, + requested_attributes: proofAttribute, }, }, }) diff --git a/packages/anoncreds-rs/package.json b/packages/anoncreds-rs/package.json index af35fc561c..49622b9fb7 100644 --- a/packages/anoncreds-rs/package.json +++ b/packages/anoncreds-rs/package.json @@ -26,14 +26,14 @@ "dependencies": { "@aries-framework/core": "0.3.3", "@aries-framework/anoncreds": "0.3.3", - "@hyperledger/anoncreds-shared": "^0.1.0-dev.5", + "@hyperledger/anoncreds-shared": "^0.1.0-dev.6", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", "rxjs": "^7.2.0", "tsyringe": "^4.7.0" }, "devDependencies": { - "@hyperledger/anoncreds-nodejs": "^0.1.0-dev.5", + "@hyperledger/anoncreds-nodejs": "^0.1.0-dev.6", "rimraf": "^4.0.7", "typescript": "~4.9.4" } diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts index 95de9d3e27..09e69ee5b7 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts @@ -1,4 +1,3 @@ -import type { LegacyIndyCredentialFormatService } from '../../../../formats/LegacyIndyCredentialFormatService' import type { CredentialProtocolOptions, CredentialStateChangedEvent } from '@aries-framework/core' import { @@ -18,15 +17,16 @@ import { JsonTransformer, InboundMessageContext, } from '@aries-framework/core' +import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../core/tests/helpers' +import { LegacyIndyCredentialFormatService } from '../../../../formats/LegacyIndyCredentialFormatService' import { V1CredentialProtocol } from '../V1CredentialProtocol' import { V1CredentialPreview, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' // Mock classes jest.mock('../../../repository/CredentialRepository') -jest.mock('../../../../ledger/services/IndyLedgerService') -jest.mock('../../../formats/indy/IndyCredentialFormatService') +jest.mock('../../../../formats/LegacyIndyCredentialFormatService') jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') jest.mock('../../../../routing/services/RoutingService') jest.mock('../../../../connections/services/ConnectionService') @@ -38,12 +38,15 @@ const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock const DispatcherMock = Dispatcher as jest.Mock +const LegacyIndyCredentialFormatServiceMock = + LegacyIndyCredentialFormatService as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() const routingService = new RoutingServiceMock() const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() +const indyCredentialFormatService = new LegacyIndyCredentialFormatServiceMock() const agentConfig = getAgentConfig('V1CredentialProtocolProposeOfferTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -102,8 +105,6 @@ describe('V1CredentialProtocolProposeOffer', () => { beforeEach(async () => { // mock function implementations mockFunction(connectionService.getById).mockResolvedValue(connectionRecord) - mockFunction(indyLedgerService.getCredentialDefinition).mockResolvedValue(credDef) - mockFunction(indyLedgerService.getSchema).mockResolvedValue(schema) credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: indyCredentialFormatService, diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index dc4ea3c4bd..73f07a0b63 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -1,79 +1,38 @@ -import type { SubjectMessage } from '../../../../../../../tests/transport/SubjectInboundTransport' -import type { - AcceptCredentialOfferOptions, - AcceptCredentialRequestOptions, - CredentialStateChangedEvent, -} from '@aries-framework/core' +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' +import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '@aries-framework/core' -import { - Agent, - AutoAcceptCredential, - CredentialEventTypes, - CredentialExchangeRecord, - CredentialState, -} from '@aries-framework/core' -import { ReplaySubject, Subject } from 'rxjs' +import { AutoAcceptCredential, CredentialExchangeRecord, CredentialState } from '@aries-framework/core' -import { SubjectInboundTransport } from '../../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, waitForCredentialRecordSubject } from '../../../../../../core/tests/helpers' -import testLogger from '../../../../../../core/tests/logger' -import { prepareForAnonCredsIssuance } from '../../../../../tests/legacyAnonCredsSetup' +import { waitForCredentialRecordSubject, testLogger } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' import { V1CredentialPreview } from '../messages' -const faberAgentOptions = getAgentOptions('Faber connection-less Credentials V1', { - endpoints: ['rxjs:faber'], -}) - -const aliceAgentOptions = getAgentOptions('Alice connection-less Credentials V1', { - endpoints: ['rxjs:alice'], -}) - const credentialPreview = V1CredentialPreview.fromRecord({ name: 'John', age: '99', }) describe('V1 Connectionless Credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let faberReplay: ReplaySubject - let aliceReplay: ReplaySubject + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceReplay: EventReplaySubject let credentialDefinitionId: string beforeEach(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await aliceAgent.initialize() - - const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Credentials V1', + holderName: 'Alice connection-less Credentials V1', attributeNames: ['name', 'age'], - issuerId: faberAgent.publicDid?.did as string, - }) - credentialDefinitionId = credentialDefinition.credentialDefinitionId - - faberReplay = new ReplaySubject() - aliceReplay = new ReplaySubject() - - faberAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(faberReplay) - aliceAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(aliceReplay) + createConnections: false, + })) }) afterEach(async () => { diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index fec47bc19f..c19b1c1065 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -1,16 +1,9 @@ +import type { EventReplaySubject } from '../../../../../../core/tests' import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' -import type { AcceptCredentialOfferOptions } from '@aries-framework/core' - -import { - AutoAcceptCredential, - CredentialState, - CredentialExchangeRecord, - JsonTransformer, - AriesFrameworkError, -} from '@aries-framework/core' - -import { waitForCredentialRecord } from '../../../../../../core/tests/helpers' -import testLogger from '../../../../../../core/tests/logger' + +import { AutoAcceptCredential, CredentialState, CredentialExchangeRecord, JsonTransformer } from '@aries-framework/core' + +import { waitForCredentialRecord, waitForCredentialRecordSubject, testLogger } from '../../../../../../core/tests' import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' import { V1CredentialPreview } from '../messages' @@ -29,7 +22,9 @@ const newCredentialPreview = V1CredentialPreview.fromRecord({ describe('V1 Credentials Auto Accept', () => { let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject let credentialDefinitionId: string let schemaId: string let faberConnectionId: string @@ -39,13 +34,16 @@ describe('V1 Credentials Auto Accept', () => { beforeAll(async () => { ;({ issuerAgent: faberAgent, + issuerReplay: faberReplay, holderAgent: aliceAgent, + holderReplay: aliceReplay, credentialDefinitionId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, } = await setupAnonCredsTests({ - issuerName: 'faber agent: always v1', - holderName: 'alice agent: always v1', + issuerName: 'Faber connection-less Credentials V1', + holderName: 'Alice connection-less Credentials V1', + attributeNames: ['name', 'age'], autoAcceptCredentials: AutoAcceptCredential.Always, })) }) @@ -114,12 +112,12 @@ describe('V1 Credentials Auto Accept', () => { protocolVersion: 'v1', }) testLogger.test('Alice waits for credential from Faber') - const aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + const aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, }) testLogger.test('Faber waits for credential ack from Alice') - const faberCredentialRecord: CredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + const faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.Done, }) @@ -157,15 +155,16 @@ describe('V1 Credentials Auto Accept', () => { beforeAll(async () => { ;({ issuerAgent: faberAgent, + issuerReplay: faberReplay, holderAgent: aliceAgent, + holderReplay: aliceReplay, credentialDefinitionId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, } = await setupAnonCredsTests({ issuerName: 'faber agent: contentApproved v1', holderName: 'alice agent: contentApproved v1', - // Not needed for this test - verifierName: 'verifier agent: always v1', + attributeNames: ['name', 'age'], autoAcceptCredentials: AutoAcceptCredential.ContentApproved, })) }) @@ -194,7 +193,7 @@ describe('V1 Credentials Auto Accept', () => { }) testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialExchangeRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -212,13 +211,13 @@ describe('V1 Credentials Auto Accept', () => { }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.CredentialReceived, }) testLogger.test('Faber waits for credential ack from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialExchangeRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.Done, }) @@ -276,7 +275,7 @@ describe('V1 Credentials Auto Accept', () => { }) testLogger.test('Alice waits for credential offer from Faber') - let aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -294,56 +293,51 @@ describe('V1 Credentials Auto Accept', () => { credentialIds: [], }) - if (aliceCredentialExchangeRecord.connectionId) { - const acceptOfferOptions: AcceptCredentialOfferOptions = { - credentialRecordId: aliceCredentialExchangeRecord.id, - } - testLogger.test('alice sends credential request to faber') - faberCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer(acceptOfferOptions) - - testLogger.test('Alice waits for credential from Faber') - aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber waits for credential ack from Alice') - faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { - threadId: faberCredentialExchangeRecord.threadId, - state: CredentialState.Done, - }) - - expect(aliceCredentialExchangeRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - metadata: { - data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { - schemaId, - credentialDefinitionId: credentialDefinitionId, - }, + testLogger.test('alice sends credential request to faber') + faberCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialExchangeRecord.id, + }) + + testLogger.test('Alice waits for credential from Faber') + aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.CredentialReceived, + }) + + testLogger.test('Faber waits for credential ack from Alice') + faberCredentialExchangeRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialExchangeRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialExchangeRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + metadata: { + data: { + '_internal/indyRequest': expect.any(Object), + '_internal/indyCredential': { + schemaId, + credentialDefinitionId: credentialDefinitionId, }, }, - credentials: [ - { - credentialRecordType: 'indy', - credentialRecordId: expect.any(String), - }, - ], - state: CredentialState.CredentialReceived, - }) - - expect(faberCredentialExchangeRecord).toMatchObject({ - type: CredentialExchangeRecord.type, - id: expect.any(String), - createdAt: expect.any(Date), - state: CredentialState.Done, - }) - } else { - throw new AriesFrameworkError('missing alice connection id') - } + }, + credentials: [ + { + credentialRecordType: 'indy', + credentialRecordId: expect.any(String), + }, + ], + state: CredentialState.CredentialReceived, + }) + + expect(faberCredentialExchangeRecord).toMatchObject({ + type: CredentialExchangeRecord.type, + id: expect.any(String), + createdAt: expect.any(Date), + state: CredentialState.Done, + }) }) test("Faber starts with V1 credential offer to Alice, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { @@ -361,7 +355,7 @@ describe('V1 Credentials Auto Accept', () => { }) testLogger.test('Alice waits for credential offer from Faber') - let aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -416,7 +410,7 @@ describe('V1 Credentials Auto Accept', () => { }) testLogger.test('Faber waits for credential proposal from Alice') - let faberCredentialExchangeRecord = await waitForCredentialRecord(faberAgent, { + let faberCredentialExchangeRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: aliceCredentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) @@ -433,7 +427,7 @@ describe('V1 Credentials Auto Accept', () => { testLogger.test('Alice waits for credential offer from Faber') - const record = await waitForCredentialRecord(aliceAgent, { + const record = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts index 0b1febb680..31aa902a05 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts @@ -1,22 +1,23 @@ -import type { AgentContext } from '../../../../../agent' -import type { AgentConfig } from '../../../../../agent/AgentConfig' -import type { ProofStateChangedEvent } from '../../../ProofEvents' -import type { CustomProofTags } from '../../../repository/ProofExchangeRecord' +import type { CustomProofTags, AgentConfig, AgentContext, ProofStateChangedEvent } from '../../../../../../core/src' import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../tests/helpers' -import { EventEmitter } from '../../../../../agent/EventEmitter' -import { InboundMessageContext } from '../../../../../agent/models/InboundMessageContext' -import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' -import { DidCommMessageRepository } from '../../../../../storage' -import { ConnectionService, DidExchangeState } from '../../../../connections' -import { ProofEventTypes } from '../../../ProofEvents' -import { PresentationProblemReportReason } from '../../../errors/PresentationProblemReportReason' -import { IndyProofFormatService } from '../../../formats/indy/IndyProofFormatService' -import { ProofState } from '../../../models/ProofState' -import { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import { ProofRepository } from '../../../repository/ProofRepository' +import { + ProofRepository, + ConnectionService, + DidCommMessageRepository, + DidExchangeState, + Attachment, + AttachmentData, + ProofState, + ProofExchangeRecord, + InboundMessageContext, + ProofEventTypes, + PresentationProblemReportReason, + EventEmitter, +} from '../../../../../../core/src' +import { getMockConnection, getAgentConfig, getAgentContext, mockFunction } from '../../../../../../core/tests' +import { LegacyIndyProofFormatService } from '../../../../formats' import { V1ProofProtocol } from '../V1ProofProtocol' import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../messages' import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' @@ -31,7 +32,7 @@ jest.mock('../../../../../storage/Repository') const ProofRepositoryMock = ProofRepository as jest.Mock const connectionServiceMock = ConnectionService as jest.Mock const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const indyProofFormatServiceMock = IndyProofFormatService as jest.Mock +const indyProofFormatServiceMock = LegacyIndyProofFormatService as jest.Mock const proofRepository = new ProofRepositoryMock() const connectionService = new connectionServiceMock() diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts index 885701ec96..71e3d3a09d 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -1,31 +1,38 @@ -import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import type { ProofStateChangedEvent } from '../../../ProofEvents' +import type { SubjectMessage } from '../../../../../../../tests/transport/SubjectInboundTransport' -import { Subject, ReplaySubject } from 'rxjs' +import { Subject } from 'rxjs' -import { SubjectInboundTransport } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../../../tests/transport/SubjectOutboundTransport' +import { SubjectInboundTransport } from '../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../tests/transport/SubjectOutboundTransport' import { - setupProofsTest, + CredentialEventTypes, + Agent, + AutoAcceptProof, + ProofState, + HandshakeProtocol, + MediatorPickupStrategy, + LinkedAttachment, + Attachment, + AttachmentData, + ProofEventTypes, +} from '../../../../../../core/src' +import { uuid } from '../../../../../../core/src/utils/uuid' +import { + testLogger, waitForProofExchangeRecordSubject, getAgentOptions, - prepareForIndyIssuance, makeConnection, + setupEventReplaySubjects, +} from '../../../../../../core/tests' +import { + getLegacyAnonCredsModules, issueLegacyAnonCredsCredential, -} from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { Agent } from '../../../../../agent/Agent' -import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' -import { LinkedAttachment } from '../../../../../utils/LinkedAttachment' -import { uuid } from '../../../../../utils/uuid' -import { HandshakeProtocol } from '../../../../connections' -import { V1CredentialPreview } from '../../../../credentials' -import { MediatorPickupStrategy } from '../../../../routing' -import { ProofEventTypes } from '../../../ProofEvents' -import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' -import { AutoAcceptProof, ProofState } from '../../../models' - -describe('Present Proof', () => { + prepareForAnonCredsIssuance, + setupAnonCredsTests, +} from '../../../../../tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../../../credentials/v1' + +describe('V1 Proofs - Connectionless - Indy', () => { let agents: Agent[] afterEach(async () => { @@ -36,42 +43,44 @@ describe('Present Proof', () => { }) test('Faber starts with connection-less proof requests to Alice', async () => { - const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs', - 'Alice connection-less Proofs', - AutoAcceptProof.Never - ) - agents = [aliceAgent, faberAgent] - testLogger.test('Faber sends presentation request to Alice') - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } + const { + holderAgent: aliceAgent, + issuerAgent: faberAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber v1 connection-less Proofs - Never', + holderName: 'Alice v1 connection-less Proofs - Never', + autoAcceptProofs: AutoAcceptProof.Never, + attributeNames: ['name', 'age'], + }) - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerReplay: faberReplay, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '99', + }, ], - }), - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.RequestReceived, + }, }) + agents = [aliceAgent, faberAgent] + testLogger.test('Faber sends presentation request to Alice') + // eslint-disable-next-line prefer-const let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ protocolVersion: 'v1', @@ -79,8 +88,28 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -93,93 +122,105 @@ describe('Present Proof', () => { await aliceAgent.receiveMessage(requestMessage.toJSON()) testLogger.test('Alice waits for presentation request from Faber') - let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + let aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.RequestReceived, + }) testLogger.test('Alice accepts presentation request from Faber') const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, }) - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.PresentationReceived, - }) - await aliceAgent.proofs.acceptRequest({ proofRecordId: aliceProofExchangeRecord.id, proofFormats: { indy: requestedCredentials.proofFormats.indy }, }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) // assert presentation is valid expect(faberProofExchangeRecord.isVerified).toBe(true) - aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.Done, - }) - // Faber accepts presentation await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) // Alice waits till it receives presentation ack - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.Done, + }) }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled', async () => { - testLogger.test('Faber sends presentation request to Alice') - - const { aliceAgent, faberAgent, aliceReplay, credDefId, faberReplay } = await setupProofsTest( - 'Faber connection-less Proofs - Auto Accept', - 'Alice connection-less Proofs - Auto Accept', - AutoAcceptProof.Always - ) - - agents = [aliceAgent, faberAgent] - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } + const { + holderAgent: aliceAgent, + issuerAgent: faberAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber v1 connection-less Proofs - Always', + holderName: 'Alice v1 connection-less Proofs - Always', + autoAcceptProofs: AutoAcceptProof.Always, + attributeNames: ['name', 'age'], + }) - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerReplay: faberReplay, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '99', + }, ], - }), - } - - const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.Done, + }, }) - const faberProofExchangeRecordPromise = waitForProofExchangeRecordSubject(faberReplay, { - state: ProofState.Done, - }) + agents = [aliceAgent, faberAgent] - // eslint-disable-next-line prefer-const - let { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + const { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ protocolVersion: 'v1', proofFormats: { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, autoAcceptProof: AutoAcceptProof.ContentApproved, @@ -193,9 +234,13 @@ describe('Present Proof', () => { await aliceAgent.receiveMessage(requestMessage.toJSON()) - await aliceProofExchangeRecordPromise + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + }) - await faberProofExchangeRecordPromise + await waitForProofExchangeRecordSubject(faberReplay, { + state: ProofState.Done, + }) }) test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { @@ -213,13 +258,8 @@ describe('Present Proof', () => { endpoints: ['rxjs:mediator'], }) - const faberMessages = new Subject() - const aliceMessages = new Subject() const mediatorMessages = new Subject() - - const subjectMap = { - 'rxjs:mediator': mediatorMessages, - } + const subjectMap = { 'rxjs:mediator': mediatorMessages } // Initialize mediator const mediatorAgent = new Agent(mediatorAgentOptions) @@ -227,6 +267,10 @@ describe('Present Proof', () => { mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() + const anonCredsModules = getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) + const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ label: 'faber invitation', handshakeProtocols: [HandshakeProtocol.Connections], @@ -237,35 +281,47 @@ describe('Present Proof', () => { handshakeProtocols: [HandshakeProtocol.Connections], }) - const faberAgentOptions = getAgentOptions(`Connectionless proofs with mediator Faber-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) + const faberAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Faber-${unique}`, + { + mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + anonCredsModules + ) - const aliceAgentOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) + const aliceAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Alice-${unique}`, + { + mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + anonCredsModules + ) const faberAgent = new Agent(faberAgentOptions) faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) await faberAgent.initialize() const aliceAgent = new Agent(aliceAgentOptions) aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) await aliceAgent.initialize() + const [faberReplay, aliceReplay] = setupEventReplaySubjects( + [faberAgent, aliceAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + agents = [aliceAgent, faberAgent, mediatorAgent] - const { definition } = await prepareForIndyIssuance(faberAgent, ['name', 'age', 'image_0', 'image_1']) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { + attributeNames: ['name', 'age', 'image_0', 'image_1'], + issuerId: faberAgent.publicDid?.did as string, + }) const [faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) expect(faberConnection.isReady).toBe(true) @@ -273,10 +329,12 @@ describe('Present Proof', () => { await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, - issuerConnectionId: faberConnection.id, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnection.id, holderAgent: aliceAgent, - credentialTemplate: { - credentialDefinitionId: definition.id, + holderReplay: aliceReplay, + offer: { + credentialDefinitionId: credentialDefinition.credentialDefinitionId, attributes: credentialPreview.attributes, linkedAttachments: [ new LinkedAttachment({ @@ -296,35 +354,6 @@ describe('Present Proof', () => { ], }, }) - const faberReplay = new ReplaySubject() - const aliceReplay = new ReplaySubject() - - faberAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(faberReplay) - aliceAgent.events.observable(ProofEventTypes.ProofStateChanged).subscribe(aliceReplay) - - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: definition.id, - }), - ], - }), - } const aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { state: ProofState.Done, @@ -341,8 +370,28 @@ describe('Present Proof', () => { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinition.credentialDefinitionId, + }, + ], + }, + }, }, }, autoAcceptProof: AutoAcceptProof.ContentApproved, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts index e7f7a6f5f1..f0d467ed7e 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts @@ -1,36 +1,29 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { AcceptProofProposalOptions, NegotiateProofProposalOptions } from '../../../ProofsApiOptions' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V1PresentationPreview } from '../models/V1PresentationPreview' -import type { CredDefId } from 'indy-sdk' +import type { AcceptProofProposalOptions } from '../../../../../../core/src' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' +import type { V1RequestPresentationMessage } from '../messages' -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { AttributeFilter } from '../../../formats/indy/models/AttributeFilter' -import { PredicateType } from '../../../formats/indy/models/PredicateType' -import { ProofAttributeInfo } from '../../../formats/indy/models/ProofAttributeInfo' -import { ProofPredicateInfo } from '../../../formats/indy/models/ProofPredicateInfo' -import { ProofState } from '../../../models/ProofState' -import { V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' +import { ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: CredDefId - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber - V1 Indy Proof Negotiation', + holderName: 'Alice - V1 Indy Proof Negotiation', + attributeNames: ['name', 'age'], + })) }) afterAll(async () => { @@ -48,30 +41,31 @@ describe('Present Proof', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { name: 'proof-request', version: '1.0', - attributes: presentationPreview.attributes.filter((attribute) => attribute.name !== 'name'), - predicates: presentationPreview.predicates, + attributes: [], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], }, }, comment: 'V1 propose proof test 1', }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - let proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) + let faberProofExchangeRecord = await faberProofExchangeRecordPromise + let proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), @@ -81,13 +75,13 @@ describe('Present Proof', () => { attributes: [ { name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + credentialDefinitionId, }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -101,70 +95,48 @@ describe('Present Proof', () => { protocolVersion: 'v1', }) - // Negotiate Proposal - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + threadId: faberProofExchangeRecord.threadId, + state: ProofState.RequestReceived, + }) - const requestProofAsResponseOptions: NegotiateProofProposalOptions = { + testLogger.test('Faber sends new proof request to Alice') + faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal({ proofRecordId: faberProofExchangeRecord.id, proofFormats: { indy: { name: 'proof-request', - nonce: '58d223e5-fc4d-4448-b74c-5eb11c6b558f', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + something: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + somethingElse: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { - threadId: faberProofExchangeRecord.threadId, - state: ProofState.RequestReceived, }) - testLogger.test('Faber sends new proof request to Alice') - faberProofExchangeRecord = await faberAgent.proofs.negotiateProposal(requestProofAsResponseOptions) - testLogger.test('Alice waits for proof request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - let request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + let request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), @@ -200,8 +172,15 @@ describe('Present Proof', () => { indy: { name: 'proof-request', version: '1.0', - attributes: presentationPreview.attributes.filter((attribute) => attribute.name === 'name'), - predicates: presentationPreview.predicates, + attributes: [], + predicates: [ + { + credentialDefinitionId, + name: 'age', + predicate: '>=', + threshold: 18, + }, + ], }, }, comment: 'V1 propose proof test 2', @@ -210,13 +189,7 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) - + proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), @@ -226,7 +199,7 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, @@ -234,7 +207,7 @@ describe('Present Proof', () => { predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -264,13 +237,7 @@ describe('Present Proof', () => { testLogger.test('Alice waits for proof request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), @@ -304,7 +271,7 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, @@ -312,7 +279,7 @@ describe('Present Proof', () => { predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -333,7 +300,7 @@ describe('Present Proof', () => { name: 'name', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -344,7 +311,7 @@ describe('Present Proof', () => { p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts index 86095b8f01..67ba394844 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts @@ -1,29 +1,27 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { V1PresentationPreview } from '../models/V1PresentationPreview' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' -import { ProofState } from '../../../models/ProofState' -import { ProofExchangeRecord } from '../../../repository' -import { V1PresentationMessage, V1ProposePresentationMessage, V1RequestPresentationMessage } from '../messages' +import { ProofState, ProofExchangeRecord } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber Agent Proofs', - 'Alice Agent Proofs' - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber - V1 Indy Proof', + holderName: 'Alice - V1 Indy Proof', + attributeNames: ['name', 'age'], + })) }) afterAll(async () => { @@ -37,19 +35,32 @@ describe('Present Proof', () => { test(`Alice Creates and sends Proof Proposal to Faber`, async () => { testLogger.test('Alice sends proof proposal to Faber') - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + let faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { name: 'ProofRequest', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, comment: 'V1 propose proof test', @@ -57,15 +68,9 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) + let faberProofExchangeRecord = await faberProofExchangeRecordPromise + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), @@ -75,19 +80,19 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, { name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + credentialDefinitionId, }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -100,11 +105,9 @@ describe('Present Proof', () => { state: ProofState.ProposalReceived, protocolVersion: 'v1', }) - }) - test(`Faber accepts the Proposal send by Alice`, async () => { // Accept Proposal - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, }) @@ -117,13 +120,7 @@ describe('Present Proof', () => { testLogger.test('Alice waits for proof request from Faber') aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const request = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1RequestPresentationMessage, - }) - + const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/request-presentation', id: expect.any(String), @@ -146,14 +143,12 @@ describe('Present Proof', () => { state: ProofState.RequestReceived, protocolVersion: 'v1', }) - }) - test(`Alice accepts presentation request from Faber`, async () => { const requestedCredentials = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId: aliceProofExchangeRecord.id, }) - const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { threadId: aliceProofExchangeRecord.threadId, state: ProofState.PresentationReceived, }) @@ -167,11 +162,7 @@ describe('Present Proof', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await faberProofExchangeRecordPromise - const presentation = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1PresentationMessage, - }) - + const presentation = await faberAgent.proofs.findPresentationMessage(faberProofExchangeRecord.id) expect(presentation).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/presentation', id: expect.any(String), @@ -204,10 +195,8 @@ describe('Present Proof', () => { state: ProofState.PresentationReceived, protocolVersion: 'v1', }) - }) - test(`Faber accepts the presentation provided by Alice`, async () => { - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { + aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { threadId: aliceProofExchangeRecord.threadId, state: ProofState.Done, }) diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts index f69048fece..8836e7a750 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts @@ -1,28 +1,27 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { ProofExchangeRecord } from '../../../repository/ProofExchangeRecord' -import type { V1PresentationPreview } from '../models/V1PresentationPreview' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' -import { ProofState } from '../../../models/ProofState' -import { V1ProposePresentationMessage } from '../messages' +import { ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber - V1 Indy Proof Request', + holderName: 'Alice - V1 Indy Proof Request', + attributeNames: ['name', 'age'], + })) }) afterAll(async () => { @@ -41,29 +40,36 @@ describe('Present Proof', () => { }) await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { name: 'ProofRequest', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, comment: 'V1 propose proof test', }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise - - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const proposal = await didCommMessageRepository.findAgentMessage(faberAgent.context, { - associatedRecordId: faberProofExchangeRecord.id, - messageClass: V1ProposePresentationMessage, - }) + const faberProofExchangeRecord = await faberProofExchangeRecordPromise + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', id: expect.any(String), @@ -73,19 +79,19 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, { name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + credentialDefinitionId, }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts index 8c9278b879..3d20b3af5b 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts @@ -1,26 +1,27 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { ProofExchangeRecord } from '../../../repository' -import type { V1PresentationPreview } from '../models' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { ProofState } from '../../../models' +import { ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' describe('Present Proof | V1ProofProtocol', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) + ;({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + credentialDefinitionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber - V1 Indy Proof Request', + holderName: 'Alice - V1 Indy Proof Request', + attributeNames: ['name', 'age'], + })) }) afterAll(async () => { @@ -38,22 +39,35 @@ describe('Present Proof | V1ProofProtocol', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { name: 'Proof Request', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, comment: 'V1 propose proof test', }) testLogger.test('Faber waits for presentation from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise + let faberProofExchangeRecord = await faberProofExchangeRecordPromise const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ @@ -65,19 +79,19 @@ describe('Present Proof | V1ProofProtocol', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, { name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + credentialDefinitionId, }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -90,9 +104,7 @@ describe('Present Proof | V1ProofProtocol', () => { state: ProofState.ProposalReceived, protocolVersion: 'v1', }) - }) - test(`Faber accepts the Proposal sent by Alice and Creates Proof Request`, async () => { const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts index 918673b0b3..9a59182b41 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts @@ -1,30 +1,50 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { V1PresentationPreview } from '../models' - -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { getGroupKeysFromIndyProofFormatData } from '../../../formats/indy/__tests__/groupKeys' -import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' -import { ProofState } from '../../../models' -import { ProofExchangeRecord } from '../../../repository' +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +import { ProofState, ProofExchangeRecord } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' import { V1ProposePresentationMessage, V1RequestPresentationMessage, V1PresentationMessage } from '../messages' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let faberProofExchangeRecord: ProofExchangeRecord - let aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: V1PresentationPreview + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest('Faber agent v1', 'Alice agent v1')) - testLogger.test('Issuing second credential') + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept Always Proofs', + holderName: 'Alice Auto Accept Always Proofs', + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { name: 'name', value: 'John' }, + { name: 'age', value: '99' }, + ], + }, + }) }) afterAll(async () => { @@ -43,20 +63,33 @@ describe('Present Proof', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + let aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, }) // Faber waits for a presentation proposal from Alice testLogger.test('Faber waits for a presentation proposal from Alice') - faberProofExchangeRecord = await faberProofExchangeRecordPromise + let faberProofExchangeRecord = await faberProofExchangeRecordPromise const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/1.0/propose-presentation', @@ -66,19 +99,19 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, { name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, + credentialDefinitionId, }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -159,15 +192,6 @@ describe('Present Proof', () => { }, }, ], - // appendedAttachments: [ - // { - // id: expect.any(String), - // filename: expect.any(String), - // data: { - // base64: expect.any(String), - // }, - // }, - // ], thread: { threadId: expect.any(String), }, @@ -222,9 +246,6 @@ describe('Present Proof', () => { const formatData = await aliceAgent.proofs.getFormatData(aliceProofExchangeRecord.id) - // eslint-disable-next-line prefer-const - let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) - expect(formatData).toMatchObject({ proposal: { indy: { @@ -235,23 +256,23 @@ describe('Present Proof', () => { 0: { name: 'name', }, - [proposeKey1]: { + something: { name: 'image_0', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [proposeKey2]: { + something_else: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -267,23 +288,23 @@ describe('Present Proof', () => { 0: { name: 'name', }, - [requestKey1]: { + something: { name: 'image_0', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [requestKey2]: { + something_else: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -301,61 +322,48 @@ describe('Present Proof', () => { }) test('Faber starts with proof request to Alice', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - let aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofExchangeRecord = await faberAgent.proofs.requestProof({ + let faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v1', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ @@ -468,42 +476,36 @@ describe('Present Proof', () => { }) test('an attribute group name matches with a predicate group name so an error is thrown', async () => { - // Age attribute - const attributes = { - age: new ProofAttributeInfo({ - name: 'age', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Age predicate - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - await expect( faberAgent.proofs.requestProof({ protocolVersion: 'v1', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -511,61 +513,48 @@ describe('Present Proof', () => { }) test('Faber starts with proof request to Alice but gets Problem Reported', async () => { - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - image_0: new ProofAttributeInfo({ - name: 'image_0', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - // Sample predicates - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const aliceProofExchangeRecordPromise = waitForProofExchangeRecord(aliceAgent, { state: ProofState.RequestReceived, }) // Faber sends a presentation request to Alice testLogger.test('Faber sends a presentation request to Alice') - faberProofExchangeRecord = await faberAgent.proofs.requestProof({ + let faberProofExchangeRecord = await faberAgent.proofs.requestProof({ protocolVersion: 'v1', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) // Alice waits for presentation request from Faber testLogger.test('Alice waits for presentation request from Faber') - aliceProofExchangeRecord = await aliceProofExchangeRecordPromise + let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise const request = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) expect(request).toMatchObject({ diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts index c8a116e8ed..407e975271 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts @@ -1,28 +1,50 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { V1PresentationPreview } from '../models' +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' -import { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' -import { AutoAcceptProof, ProofState } from '../../../models' +import { AutoAcceptProof, ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' describe('Auto accept present proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let faberConnectionId: string + let aliceConnectionId: string + let credentialDefinitionId: string describe("Auto accept on 'always'", () => { beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Always Proofs', - 'Alice Auto Accept Always Proofs', - AutoAcceptProof.Always - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept Always Proofs', + holderName: 'Alice Auto Accept Always Proofs', + autoAcceptProofs: AutoAcceptProof.Always, + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { name: 'name', value: 'John' }, + { name: 'age', value: '99' }, + ], + }, + }) }) afterAll(async () => { await faberAgent.shutdown() @@ -35,14 +57,27 @@ describe('Auto accept present proof', () => { testLogger.test('Alice sends presentation proposal to Faber') await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { name: 'abc', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, }) @@ -57,38 +92,36 @@ describe('Auto accept present proof', () => { test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'always'", async () => { testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } await faberAgent.proofs.requestProof({ protocolVersion: 'v1', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -104,12 +137,35 @@ describe('Auto accept present proof', () => { describe("Auto accept on 'contentApproved'", () => { beforeAll(async () => { testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Content Approved Proofs', - 'Alice Auto Accept Content Approved Proofs', - AutoAcceptProof.ContentApproved - )) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Auto Accept ContentApproved Proofs', + holderName: 'Alice Auto Accept ContentApproved Proofs', + autoAcceptProofs: AutoAcceptProof.ContentApproved, + attributeNames: ['name', 'age'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { name: 'name', value: 'John' }, + { name: 'age', value: '99' }, + ], + }, + }) }) afterAll(async () => { testLogger.test('Shutting down both agents') @@ -123,14 +179,27 @@ describe('Auto accept present proof', () => { testLogger.test('Alice sends presentation proposal to Faber') const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', proofFormats: { indy: { name: 'abc', version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, }) @@ -152,38 +221,36 @@ describe('Auto accept present proof', () => { test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'contentApproved'", async () => { testLogger.test('Faber sends presentation request to Alice') - const attributes = { - name: new ProofAttributeInfo({ - name: 'name', - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - const predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } await faberAgent.proofs.requestProof({ protocolVersion: 'v1', - connectionId: faberConnection.id, + connectionId: faberConnectionId, proofFormats: { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 61a0442771..32c3471c39 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -143,8 +143,8 @@ export async function presentLegacyAnonCredsProof({ proofFormats: { indy: { name: 'Test Proof Request', - requestedAttributes: attributes, - requestedPredicates: predicates, + requested_attributes: attributes, + requested_predicates: predicates, version: '1.0', }, }, diff --git a/packages/askar/package.json b/packages/askar/package.json index af83ba53ea..9e4024caea 100644 --- a/packages/askar/package.json +++ b/packages/askar/package.json @@ -25,7 +25,7 @@ }, "dependencies": { "@aries-framework/core": "0.3.3", - "@hyperledger/aries-askar-shared": "^0.1.0-dev.1", + "@hyperledger/aries-askar-shared": "^0.1.0-dev.3", "bn.js": "^5.2.1", "class-transformer": "^0.5.1", "class-validator": "^0.14.0", @@ -34,7 +34,7 @@ }, "devDependencies": { "@types/bn.js": "^5.1.0", - "@hyperledger/aries-askar-nodejs": "^0.1.0-dev.1", + "@hyperledger/aries-askar-nodejs": "^0.1.0-dev.3", "reflect-metadata": "^0.1.13", "rimraf": "^4.0.7", "typescript": "~4.9.4" diff --git a/packages/askar/src/wallet/AskarWallet.ts b/packages/askar/src/wallet/AskarWallet.ts index c84370cadf..d2a970d447 100644 --- a/packages/askar/src/wallet/AskarWallet.ts +++ b/packages/askar/src/wallet/AskarWallet.ts @@ -361,7 +361,10 @@ export class AskarWallet implements Wallet { const algorithm = keyAlgFromString(keyType) // Create key from seed - const key = seed ? AskarKey.fromSeed({ seed: Buffer.from(seed), algorithm }) : AskarKey.generate(algorithm) + const key = seed + ? // FIXME: we call fromSecretBytes using the secretKey + AskarKey.fromSecretBytes({ secretKey: Buffer.from(seed), algorithm }) + : AskarKey.generate(algorithm) // Store key await this.session.insertKey({ key, name: encodeToBase58(key.publicBytes) }) diff --git a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index 936dfbe22e..74216611e6 100644 --- a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -1,5 +1,5 @@ import type { W3cCredentialRepository } from '../../core/src/modules/vc/repository' -import type { AgentContext } from '@aries-framework/core' +import type { AgentContext, Wallet } from '@aries-framework/core' import { VERIFICATION_METHOD_TYPE_ED25519_VERIFICATION_KEY_2018, @@ -17,14 +17,15 @@ import { LinkedDataProof, W3cPresentation, W3cVerifiablePresentation, - IndyWallet, Ed25519Signature2018, } from '@aries-framework/core' +import indySdk from 'indy-sdk' import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuiteRegistry' import { W3cVcModuleConfig } from '../../core/src/modules/vc/W3cVcModuleConfig' import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import { IndySdkWallet } from '../../indy-sdk/src' import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2SigningProvider } from '../src' import { BbsBlsSignature2020Fixtures } from './fixtures' @@ -59,15 +60,14 @@ const signingProviderRegistry = new SigningProviderRegistry([new Bls12381g2Signi const agentConfig = getAgentConfig('BbsSignaturesE2eTest') describeSkipNode17And18('BBS W3cCredentialService', () => { - let wallet: IndyWallet + let wallet: Wallet let agentContext: AgentContext let w3cCredentialService: W3cCredentialService const seed = 'testseed000000000000000000000001' beforeAll(async () => { - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, signingProviderRegistry) - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - await wallet.createAndOpen(agentConfig.walletConfig!) + wallet = new IndySdkWallet(indySdk, agentConfig.logger, signingProviderRegistry) + await wallet.createAndOpen(agentConfig.walletConfig) agentContext = getAgentContext({ agentConfig, wallet, diff --git a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts index db67e0c5a1..205598c7a8 100644 --- a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts @@ -1,4 +1,4 @@ -import type { WalletConfig } from '@aries-framework/core' +import type { Wallet, WalletConfig } from '@aries-framework/core' import { KeyDerivationMethod, @@ -6,12 +6,12 @@ import { WalletError, TypedArrayEncoder, SigningProviderRegistry, - IndyWallet, } from '@aries-framework/core' -import { agentDependencies } from '@aries-framework/node' import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' +import indySdk from 'indy-sdk' import testLogger from '../../core/tests/logger' +import { IndySdkWallet } from '../../indy-sdk/src' import { Bls12381g2SigningProvider } from '../src' import { describeSkipNode17And18 } from './util' @@ -25,25 +25,21 @@ const walletConfig: WalletConfig = { } describeSkipNode17And18('BBS Signing Provider', () => { - let indyWallet: IndyWallet + let wallet: Wallet const seed = 'sample-seed' const message = TypedArrayEncoder.fromString('sample-message') beforeEach(async () => { - indyWallet = new IndyWallet( - agentDependencies, - testLogger, - new SigningProviderRegistry([new Bls12381g2SigningProvider()]) - ) - await indyWallet.createAndOpen(walletConfig) + wallet = new IndySdkWallet(indySdk, testLogger, new SigningProviderRegistry([new Bls12381g2SigningProvider()])) + await wallet.createAndOpen(walletConfig) }) afterEach(async () => { - await indyWallet.delete() + await wallet.delete() }) test('Create bls12381g2 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 })).resolves.toMatchObject({ + await expect(wallet.createKey({ seed, keyType: KeyType.Bls12381g2 })).resolves.toMatchObject({ publicKeyBase58: 't54oLBmhhRcDLUyWTvfYRWw8VRXRy1p43pVm62hrpShrYPuHe9WNAgS33DPfeTK6xK7iPrtJDwCHZjYgbFYDVTJHxXex9xt2XEGF8D356jBT1HtqNeucv3YsPLfTWcLcpFA', keyType: KeyType.Bls12381g2, @@ -51,12 +47,12 @@ describeSkipNode17And18('BBS Signing Provider', () => { }) test('Fail to create bls12381g1g2 keypair', async () => { - await expect(indyWallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 })).rejects.toThrowError(WalletError) + await expect(wallet.createKey({ seed, keyType: KeyType.Bls12381g1g2 })).rejects.toThrowError(WalletError) }) test('Create a signature with a bls12381g2 keypair', async () => { - const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) - const signature = await indyWallet.sign({ + const bls12381g2Key = await wallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) + const signature = await wallet.sign({ data: message, key: bls12381g2Key, }) @@ -64,11 +60,11 @@ describeSkipNode17And18('BBS Signing Provider', () => { }) test('Verify a signed message with a bls12381g2 publicKey', async () => { - const bls12381g2Key = await indyWallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) - const signature = await indyWallet.sign({ + const bls12381g2Key = await wallet.createKey({ seed, keyType: KeyType.Bls12381g2 }) + const signature = await wallet.sign({ data: message, key: bls12381g2Key, }) - await expect(indyWallet.verify({ key: bls12381g2Key, data: message, signature })).resolves.toStrictEqual(true) + await expect(wallet.verify({ key: bls12381g2Key, data: message, signature })).resolves.toStrictEqual(true) }) }) diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index a22408ce87..195dbac91d 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -1,46 +1,76 @@ -import type { ConnectionRecord } from '../../core/src/modules/connections' -import type { JsonCredential, JsonLdCredentialDetailFormat } from '../../core/src/modules/credentials/formats/jsonld' -import type { Wallet } from '../../core/src/wallet' -import type { CredentialTestsAgent } from '../../core/tests/helpers' +import type { V2IssueCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage' +import type { EventReplaySubject, JsonLdTestsAgent } from '../../core/tests' -import { InjectionSymbols } from '../../core/src/constants' import { KeyType } from '../../core/src/crypto' import { CredentialState } from '../../core/src/modules/credentials/models' -import { V2IssueCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2IssueCredentialMessage' -import { V2OfferCredentialMessage } from '../../core/src/modules/credentials/protocol/v2/messages/V2OfferCredentialMessage' import { CredentialExchangeRecord } from '../../core/src/modules/credentials/repository/CredentialExchangeRecord' -import { DidKey } from '../../core/src/modules/dids' import { CREDENTIALS_CONTEXT_V1_URL, SECURITY_CONTEXT_BBS_URL } from '../../core/src/modules/vc' -import { DidCommMessageRepository } from '../../core/src/storage' import { JsonTransformer } from '../../core/src/utils/JsonTransformer' -import { setupCredentialTests, waitForCredentialRecord } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' +import { waitForCredentialRecordSubject, setupJsonLdTests, testLogger } from '../../core/tests' import { describeSkipNode17And18 } from './util' -let faberAgent: CredentialTestsAgent -let aliceAgent: CredentialTestsAgent -let aliceConnection: ConnectionRecord +let faberAgent: JsonLdTestsAgent +let faberReplay: EventReplaySubject +let aliceAgent: JsonLdTestsAgent +let aliceReplay: EventReplaySubject +let aliceConnectionId: string let aliceCredentialRecord: CredentialExchangeRecord let faberCredentialRecord: CredentialExchangeRecord +const signCredentialOptions = { + credential: { + '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], + id: 'https://issuer.oidp.uscis.gov/credentials/83627465', + type: ['VerifiableCredential', 'PermanentResidentCard'], + issuer: + 'did:key:zUC72Q7XD4PE4CrMiDVXuvZng3sBvMmaGgNeTUJuzavH2BS7ThbHL9FhsZM9QYY5fqAQ4MB8M9oudz3tfuaX36Ajr97QRW7LBt6WWmrtESe6Bs5NYzFtLWEmeVtvRYVAgjFcJSa', + issuanceDate: '2019-12-03T12:19:52Z', + expirationDate: '2029-12-03T12:19:52Z', + identifier: '83627465', + name: 'Permanent Resident Card', + credentialSubject: { + id: 'did:example:b34ca6cd37bbf23', + type: ['PermanentResident', 'Person'], + givenName: 'JOHN', + familyName: 'SMITH', + gender: 'Male', + image: 'data:image/png;base64,iVBORw0KGgokJggg==', + residentSince: '2015-01-01', + description: 'Government of Example Permanent Resident Card.', + lprCategory: 'C09', + lprNumber: '999-999-999', + commuterClassification: 'C1', + birthCountry: 'Bahamas', + birthDate: '1958-07-17', + }, + }, + options: { + proofType: 'BbsBlsSignature2020', + proofPurpose: 'assertionMethod', + }, +} + describeSkipNode17And18('credentials, BBS+ signature', () => { - let wallet - let issuerDidKey: DidKey - let didCommMessageRepository: DidCommMessageRepository - let signCredentialOptions: JsonLdCredentialDetailFormat - const seed = 'testseed000000000000000000000001' beforeAll(async () => { - ;({ faberAgent, aliceAgent, aliceConnection } = await setupCredentialTests( - 'Faber Agent Credentials LD BBS+', - 'Alice Agent Credentials LD BBS+' - )) - wallet = faberAgent.dependencyManager.resolve(InjectionSymbols.Wallet) - await wallet.createKey({ keyType: KeyType.Ed25519, seed }) - const key = await wallet.createKey({ keyType: KeyType.Bls12381g2, seed }) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + holderIssuerConnectionId: aliceConnectionId, + } = await setupJsonLdTests({ + issuerName: 'Faber Agent Credentials LD BBS+', + holderName: 'Alice Agent Credentials LD BBS+', + })) - issuerDidKey = new DidKey(key) + await faberAgent.context.wallet.createKey({ keyType: KeyType.Ed25519, seed: 'testseed000000000000000000000001' }) + await faberAgent.context.wallet.createKey({ + keyType: KeyType.Bls12381g2, + seed: 'testseed000000000000000000000001', + }) }) + afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -50,45 +80,8 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { test('Alice starts with V2 (ld format, BbsBlsSignature2020 signature) credential proposal to Faber', async () => { testLogger.test('Alice sends (v2 jsonld) credential proposal to Faber') - // set the propose options - - const TEST_LD_DOCUMENT: JsonCredential = { - '@context': [CREDENTIALS_CONTEXT_V1_URL, 'https://w3id.org/citizenship/v1', SECURITY_CONTEXT_BBS_URL], - id: 'https://issuer.oidp.uscis.gov/credentials/83627465', - type: ['VerifiableCredential', 'PermanentResidentCard'], - issuer: issuerDidKey.did, - issuanceDate: '2019-12-03T12:19:52Z', - expirationDate: '2029-12-03T12:19:52Z', - identifier: '83627465', - name: 'Permanent Resident Card', - credentialSubject: { - id: 'did:example:b34ca6cd37bbf23', - type: ['PermanentResident', 'Person'], - givenName: 'JOHN', - familyName: 'SMITH', - gender: 'Male', - image: 'data:image/png;base64,iVBORw0KGgokJggg==', - residentSince: '2015-01-01', - description: 'Government of Example Permanent Resident Card.', - lprCategory: 'C09', - lprNumber: '999-999-999', - commuterClassification: 'C1', - birthCountry: 'Bahamas', - birthDate: '1958-07-17', - }, - } - signCredentialOptions = { - credential: TEST_LD_DOCUMENT, - options: { - proofType: 'BbsBlsSignature2020', - proofPurpose: 'assertionMethod', - }, - } - - testLogger.test('Alice sends (v2, Indy) credential proposal to Faber') - const credentialExchangeRecord = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { jsonld: signCredentialOptions, @@ -96,16 +89,17 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { comment: 'v2 propose credential test for W3C Credentials', }) - expect(credentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(credentialExchangeRecord.connectionId).toEqual(aliceConnectionId) expect(credentialExchangeRecord.protocolVersion).toEqual('v2') expect(credentialExchangeRecord.state).toEqual(CredentialState.ProposalSent) expect(credentialExchangeRecord.threadId).not.toBeNull() testLogger.test('Faber waits for credential proposal from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: credentialExchangeRecord.threadId, state: CredentialState.ProposalReceived, }) + testLogger.test('Faber sends credential offer to Alice') await faberAgent.credentials.acceptProposal({ credentialRecordId: faberCredentialRecord.id, @@ -113,18 +107,12 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { }) testLogger.test('Alice waits for credential offer from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.OfferReceived, }) - didCommMessageRepository = faberAgent.dependencyManager.resolve(DidCommMessageRepository) - - const offerMessage = await didCommMessageRepository.findAgentMessage(aliceAgent.context, { - associatedRecordId: aliceCredentialRecord.id, - messageClass: V2OfferCredentialMessage, - }) - + const offerMessage = await faberAgent.credentials.findOfferMessage(aliceCredentialRecord.id) expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', '@id': expect.any(String), @@ -162,37 +150,32 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { expect(aliceCredentialRecord.id).not.toBeNull() expect(aliceCredentialRecord.type).toBe(CredentialExchangeRecord.type) - if (!aliceCredentialRecord.connectionId) { - throw new Error('Missing Connection Id') - } - - const offerCredentialExchangeRecord: CredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ + const offerCredentialExchangeRecord = await aliceAgent.credentials.acceptOffer({ credentialRecordId: aliceCredentialRecord.id, credentialFormats: { jsonld: undefined, }, }) - expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnection.id) + expect(offerCredentialExchangeRecord.connectionId).toEqual(aliceConnectionId) expect(offerCredentialExchangeRecord.protocolVersion).toEqual('v2') expect(offerCredentialExchangeRecord.state).toEqual(CredentialState.RequestSent) expect(offerCredentialExchangeRecord.threadId).not.toBeNull() testLogger.test('Faber waits for credential request from Alice') - await waitForCredentialRecord(faberAgent, { + await waitForCredentialRecordSubject(faberReplay, { threadId: aliceCredentialRecord.threadId, state: CredentialState.RequestReceived, }) testLogger.test('Faber sends credential to Alice') - await faberAgent.credentials.acceptRequest({ credentialRecordId: faberCredentialRecord.id, comment: 'V2 W3C Offer', }) testLogger.test('Alice waits for credential from Faber') - aliceCredentialRecord = await waitForCredentialRecord(aliceAgent, { + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.CredentialReceived, }) @@ -201,7 +184,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { await aliceAgent.credentials.acceptCredential({ credentialRecordId: aliceCredentialRecord.id }) testLogger.test('Faber waits for credential ack from Alice') - faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { threadId: faberCredentialRecord.threadId, state: CredentialState.Done, }) @@ -214,14 +197,11 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { state: CredentialState.CredentialReceived, }) - const credentialMessage = await didCommMessageRepository.getAgentMessage(faberAgent.context, { - associatedRecordId: faberCredentialRecord.id, - messageClass: V2IssueCredentialMessage, - }) - - const w3cCredential = credentialMessage.credentialAttachments[0].getDataAsJson() + const credentialMessage = await faberAgent.credentials.findCredentialMessage(faberCredentialRecord.id) + const w3cCredential = (credentialMessage as V2IssueCredentialMessage).credentialAttachments[0].getDataAsJson() expect(w3cCredential).toMatchObject({ + todo: 'todo', context: [ 'https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts index 62f6c95194..fb8761fa3e 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -84,7 +84,7 @@ describe('V2 Connectionless Proofs - Indy', () => { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -94,7 +94,7 @@ describe('V2 Connectionless Proofs - Indy', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', @@ -199,7 +199,7 @@ describe('V2 Connectionless Proofs - Indy', () => { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -209,7 +209,7 @@ describe('V2 Connectionless Proofs - Indy', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', @@ -359,7 +359,7 @@ describe('V2 Connectionless Proofs - Indy', () => { indy: { name: 'test-proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -369,7 +369,7 @@ describe('V2 Connectionless Proofs - Indy', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts index 3aa53817df..b7bd3e5d41 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts @@ -158,7 +158,7 @@ describe('V2 Proofs Negotiation - Indy', () => { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -168,7 +168,7 @@ describe('V2 Proofs Negotiation - Indy', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts index bc33293db5..45a63b55a5 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts @@ -108,7 +108,7 @@ describe('Auto accept present proof', () => { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -118,7 +118,7 @@ describe('Auto accept present proof', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', @@ -247,7 +247,7 @@ describe('Auto accept present proof', () => { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -257,7 +257,7 @@ describe('Auto accept present proof', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts index 32cbc38c4a..7218a06fac 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts @@ -373,7 +373,7 @@ describe('Present Proof', () => { indy: { name: 'Proof Request', version: '1.0.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -391,7 +391,7 @@ describe('Present Proof', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', @@ -537,7 +537,7 @@ describe('Present Proof', () => { indy: { name: 'Proof Request', version: '1.0.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -555,7 +555,7 @@ describe('Present Proof', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', @@ -661,7 +661,7 @@ describe('Present Proof', () => { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -679,7 +679,7 @@ describe('Present Proof', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', diff --git a/packages/core/tests/jsonld.ts b/packages/core/tests/jsonld.ts index c1112d76e3..aa08dc84e6 100644 --- a/packages/core/tests/jsonld.ts +++ b/packages/core/tests/jsonld.ts @@ -1,6 +1,10 @@ import type { EventReplaySubject } from './events' import type { AutoAcceptCredential, AutoAcceptProof, ConnectionRecord } from '../src' +import indySdk from 'indy-sdk' + +import { BbsModule } from '../../bbs-signatures/src/BbsModule' +import { IndySdkModule } from '../../indy-sdk/src' import { CacheModule, CredentialEventTypes, @@ -39,6 +43,10 @@ export const getJsonLdModules = ({ cache: new CacheModule({ cache: new InMemoryLruCache({ limit: 100 }), }), + indySdk: new IndySdkModule({ + indySdk, + }), + bbs: new BbsModule(), } as const) interface SetupJsonLdTestsReturn { diff --git a/packages/core/tests/proofs-sub-protocol.test.ts b/packages/core/tests/proofs-sub-protocol.test.ts index 1a0311e656..244eacd496 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -171,7 +171,7 @@ describe('Present Proof Subprotocol', () => { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -181,7 +181,7 @@ describe('Present Proof Subprotocol', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', @@ -347,7 +347,7 @@ describe('Present Proof Subprotocol', () => { indy: { name: 'proof-request', version: '1.0', - requestedAttributes: { + requested_attributes: { name: { name: 'name', restrictions: [ @@ -357,7 +357,7 @@ describe('Present Proof Subprotocol', () => { ], }, }, - requestedPredicates: { + requested_predicates: { age: { name: 'age', p_type: '>=', diff --git a/packages/indy-sdk/src/ledger/IndySdkPool.ts b/packages/indy-sdk/src/ledger/IndySdkPool.ts index b5f6f328d3..757c120df9 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPool.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPool.ts @@ -37,7 +37,7 @@ export class IndySdkPool { private fileSystem: FileSystem private poolConfig: IndySdkPoolConfig private _poolHandle?: number - private poolConnected?: Promise + private poolConnected?: Promise public authorAgreement?: AuthorAgreement | null public constructor( @@ -118,7 +118,7 @@ export class IndySdkPool { try { this._poolHandle = await this.indySdk.openPoolLedger(poolName) - return this._poolHandle + return } catch (error) { if (!isIndyError(error, 'PoolLedgerNotCreatedError')) { throw isIndyError(error) ? new IndySdkError(error) : error @@ -131,7 +131,7 @@ export class IndySdkPool { try { await this.indySdk.createPoolLedgerConfig(poolName, { genesis_txn: genesisPath }) this._poolHandle = await this.indySdk.openPoolLedger(poolName) - return this._poolHandle + return } catch (error) { throw isIndyError(error) ? new IndySdkError(error) : error } @@ -175,9 +175,8 @@ export class IndySdkPool { } } - if (!this._poolHandle) { - return this.connect() - } + if (!this._poolHandle) await this.connect() + if (!this._poolHandle) throw new IndySdkPoolError('Pool handle not set after connection') return this._poolHandle } diff --git a/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts index 4b7f822f0e..23a1aebdf8 100644 --- a/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts +++ b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts @@ -7,9 +7,9 @@ import { TypedArrayEncoder, KeyDerivationMethod, } from '@aries-framework/core' +import indySdk from 'indy-sdk' import testLogger from '../../../../core/tests/logger' -import { agentDependencies } from '../../../../node/src' import { IndySdkWallet } from '../IndySdkWallet' // use raw key derivation method to speed up wallet creating / opening / closing between tests @@ -35,7 +35,7 @@ describe('IndySdkWallet', () => { const message = TypedArrayEncoder.fromString('sample-message') beforeEach(async () => { - indySdkWallet = new IndySdkWallet(agentDependencies.indy, testLogger, new SigningProviderRegistry([])) + indySdkWallet = new IndySdkWallet(indySdk, testLogger, new SigningProviderRegistry([])) await indySdkWallet.createAndOpen(walletConfig) }) @@ -111,7 +111,7 @@ describe('IndySdkWallet with custom Master Secret Id', () => { let indySdkWallet: IndySdkWallet beforeEach(async () => { - indySdkWallet = new IndySdkWallet(agentDependencies.indy, testLogger, new SigningProviderRegistry([])) + indySdkWallet = new IndySdkWallet(indySdk, testLogger, new SigningProviderRegistry([])) await indySdkWallet.createAndOpen(walletConfigWithMasterSecretId) }) diff --git a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts index d3a5629cd5..99639dbb8a 100644 --- a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts @@ -6,12 +6,12 @@ import { generateKeyPairFromSeed } from '@stablelib/ed25519' import { getAgentOptions } from '../../core/tests/helpers' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' -import { indySdkModules } from './setupIndySdkModule' +import { getIndySdkModules } from './setupIndySdkModule' -const agentOptions = getAgentOptions('Faber Dids Registrar', {}, indySdkModules) +const agentOptions = getAgentOptions('Faber Dids Registrar', {}, getIndySdkModules()) describe('dids', () => { - let agent: Agent + let agent: Agent> beforeAll(async () => { agent = new Agent(agentOptions) @@ -51,7 +51,6 @@ describe('dids', () => { }, }) - console.log(did) expect(JsonTransformer.toJSON(did)).toMatchObject({ didDocumentMetadata: { qualifiedIndyDid: `did:indy:localhost:${indyDid}`, diff --git a/packages/indy-vdr/package.json b/packages/indy-vdr/package.json index 418d681c36..a753c5e3b4 100644 --- a/packages/indy-vdr/package.json +++ b/packages/indy-vdr/package.json @@ -26,10 +26,10 @@ "dependencies": { "@aries-framework/anoncreds": "0.3.3", "@aries-framework/core": "0.3.3", - "@hyperledger/indy-vdr-shared": "^0.1.0-dev.4" + "@hyperledger/indy-vdr-shared": "^0.1.0-dev.6" }, "devDependencies": { - "@hyperledger/indy-vdr-nodejs": "^0.1.0-dev.4", + "@hyperledger/indy-vdr-nodejs": "^0.1.0-dev.6", "rimraf": "^4.0.7", "typescript": "~4.9.4" } diff --git a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts index 1106b498cd..3747dfcb2e 100644 --- a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts +++ b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts @@ -317,9 +317,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { schemaId: `${schemaMetadata.indyLedgerSeqNo}`, type: 'CL', tag: options.credentialDefinition.tag, - value: { - primary: options.credentialDefinition.value, - }, + value: options.credentialDefinition.value, }, }) @@ -335,7 +333,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { credentialDefinitionState: { credentialDefinition: options.credentialDefinition, state: 'failed', - reason: `didNotFound: unable to resolve did did:sov${options.credentialDefinition.issuerId}: ${didResult.didResolutionMetadata.message}`, + reason: `didNotFound: unable to resolve did did:sov:${options.credentialDefinition.issuerId}: ${didResult.didResolutionMetadata.message}`, }, } } diff --git a/packages/indy-vdr/src/pool/IndyVdrPool.ts b/packages/indy-vdr/src/pool/IndyVdrPool.ts index 08dcb03a4a..e5dfaade1a 100644 --- a/packages/indy-vdr/src/pool/IndyVdrPool.ts +++ b/packages/indy-vdr/src/pool/IndyVdrPool.ts @@ -55,26 +55,27 @@ export class IndyVdrPool { return this.poolConfig } - public async connect() { + public connect() { + if (this._pool) { + throw new IndyVdrError('Cannot connect to pool, already connected.') + } + this._pool = new PoolCreate({ parameters: { transactions: this.config.genesisTransactions, }, }) - - return this.pool.handle } private get pool(): indyVdrPool { - if (!this._pool) { - throw new IndyVdrError('Pool is not connected. Make sure to call .connect() first') - } + if (!this._pool) this.connect() + if (!this._pool) throw new IndyVdrError('Pool is not connected.') return this._pool } public close() { - if (!this.pool) { + if (!this._pool) { throw new IndyVdrError("Can't close pool. Pool is not connected") } diff --git a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts index 661384e480..e9ac898aad 100644 --- a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts +++ b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts @@ -87,7 +87,7 @@ export class IndyVdrPoolService { // one or more of the ledgers returned an unknown error throw new IndyVdrError( - `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers`, + `Unknown error retrieving did '${did}' from '${rejectedOtherThanNotFound.length}' of '${pools.length}' ledgers. ${rejectedOtherThanNotFound[0].reason}`, { cause: rejectedOtherThanNotFound[0].reason } ) } diff --git a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts index 564540c467..105f31f5c2 100644 --- a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts @@ -4,14 +4,15 @@ import { CacheModuleConfig, InMemoryLruCache, JsonTransformer, - IndyWallet, KeyType, SigningProviderRegistry, } from '@aries-framework/core' +import indySdk from 'indy-sdk' import { parseDid } from '../../core/src/modules/dids/domain/parse' -import { agentDependencies, getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' import testLogger from '../../core/tests/logger' +import { IndySdkWallet } from '../../indy-sdk/src' import { IndyVdrSovDidResolver } from '../src/dids' import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' @@ -19,7 +20,7 @@ import { indyDidFromPublicKeyBase58 } from '../src/utils/did' import { createDidOnLedger, indyVdrModuleConfig } from './helpers' const logger = testLogger -const wallet = new IndyWallet(agentDependencies, logger, new SigningProviderRegistry([])) +const wallet = new IndySdkWallet(indySdk, logger, new SigningProviderRegistry([])) const agentConfig = getAgentConfig('IndyVdrResolver E2E', { logger }) const cache = new InMemoryLruCache({ limit: 200 }) diff --git a/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts index 3afd7d86e2..a98d12e5cc 100644 --- a/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts @@ -1,10 +1,12 @@ import type { Key } from '@aries-framework/core' -import { IndyWallet, KeyType, SigningProviderRegistry } from '@aries-framework/core' +import { KeyType, SigningProviderRegistry } from '@aries-framework/core' import { GetNymRequest, NymRequest, SchemaRequest, CredentialDefinitionRequest } from '@hyperledger/indy-vdr-shared' +import indySdk from 'indy-sdk' -import { agentDependencies, genesisTransactions, getAgentConfig, getAgentContext } from '../../core/tests/helpers' +import { genesisTransactions, getAgentConfig, getAgentContext } from '../../core/tests/helpers' import testLogger from '../../core/tests/logger' +import { IndySdkWallet } from '../../indy-sdk/src' import { IndyVdrPool } from '../src/pool' import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' @@ -12,7 +14,7 @@ import { indyDidFromPublicKeyBase58 } from '../src/utils/did' import { indyVdrModuleConfig } from './helpers' const indyVdrPoolService = new IndyVdrPoolService(testLogger, indyVdrModuleConfig) -const wallet = new IndyWallet(agentDependencies, testLogger, new SigningProviderRegistry([])) +const wallet = new IndySdkWallet(indySdk, testLogger, new SigningProviderRegistry([])) const agentConfig = getAgentConfig('IndyVdrPoolService') const agentContext = getAgentContext({ wallet, agentConfig }) diff --git a/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts index bd3f071a01..881d1b4e21 100644 --- a/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts +++ b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts @@ -1,17 +1,13 @@ -import type { AgentConfig, AgentContext, Repository } from '@aries-framework/core' +import type { AgentConfig, AgentContext, Repository, Wallet } from '@aries-framework/core' import type { QuestionAnswerStateChangedEvent, ValidResponse } from '@aries-framework/question-answer' -import { - EventEmitter, - IndyWallet, - SigningProviderRegistry, - InboundMessageContext, - DidExchangeState, -} from '@aries-framework/core' +import { EventEmitter, SigningProviderRegistry, InboundMessageContext, DidExchangeState } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' +import indySdk from 'indy-sdk' import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../core/tests/helpers' +import { IndySdkWallet } from '../../../indy-sdk/src' import { QuestionAnswerRecord, @@ -34,7 +30,7 @@ describe('QuestionAnswerService', () => { state: DidExchangeState.Completed, }) - let wallet: IndyWallet + let wallet: Wallet let agentConfig: AgentConfig let questionAnswerRepository: Repository let questionAnswerService: QuestionAnswerService @@ -65,7 +61,7 @@ describe('QuestionAnswerService', () => { beforeAll(async () => { agentConfig = getAgentConfig('QuestionAnswerServiceTest') - wallet = new IndyWallet(agentConfig.agentDependencies, agentConfig.logger, new SigningProviderRegistry([])) + wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) agentContext = getAgentContext() // eslint-disable-next-line @typescript-eslint/no-non-null-assertion await wallet.createAndOpen(agentConfig.walletConfig!) diff --git a/yarn.lock b/yarn.lock index 44caceb35e..468c9592e0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -858,12 +858,12 @@ resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340" integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q== -"@hyperledger/anoncreds-nodejs@^0.1.0-dev.5": - version "0.1.0-dev.5" - resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-nodejs/-/anoncreds-nodejs-0.1.0-dev.5.tgz#71b6dbcfab72f826bcead2b79dafe47fc8f1567c" - integrity sha512-BX/OxQjTMoCAJP4fgJEcGct1ZnNYgybO+VLD5LyzHW4nmTFOJo3TXy5IYHAJv61b/uNUQ/2GMYmPKLSLOVExNw== +"@hyperledger/anoncreds-nodejs@^0.1.0-dev.6": + version "0.1.0-dev.6" + resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-nodejs/-/anoncreds-nodejs-0.1.0-dev.6.tgz#db90e9de4a05e1446132048c07f54503afee4272" + integrity sha512-0BKTEVf1ovkGZGEsMK1jj545jIX48nYQwjKakMmKnSFrb62mtS7VkZ+5kQWp8fGzcTjr3h6Lo/G7TaMIDgQ6Ww== dependencies: - "@hyperledger/anoncreds-shared" "0.1.0-dev.5" + "@hyperledger/anoncreds-shared" "0.1.0-dev.6" "@mapbox/node-pre-gyp" "^1.0.10" ffi-napi "4.0.3" node-cache "5.1.2" @@ -871,17 +871,17 @@ ref-napi "3.0.3" ref-struct-di "1.1.1" -"@hyperledger/anoncreds-shared@0.1.0-dev.5", "@hyperledger/anoncreds-shared@^0.1.0-dev.5": - version "0.1.0-dev.5" - resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-shared/-/anoncreds-shared-0.1.0-dev.5.tgz#653a8ec1ac83eae3af8aabb7fa5609bb0c3453b2" - integrity sha512-NPbjZd7WJN/eKtHtYcOy+E9Ebh0YkZ7bre59zWD3w66aiehZrSLbL5+pjY9shrSIN1h05t0XnvT1JZKTtXgqcQ== +"@hyperledger/anoncreds-shared@0.1.0-dev.6", "@hyperledger/anoncreds-shared@^0.1.0-dev.6": + version "0.1.0-dev.6" + resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-shared/-/anoncreds-shared-0.1.0-dev.6.tgz#2e6afb4641cc25daef4074a32990ec078d2fd94e" + integrity sha512-4YZ2kzhOOrGRL//n/Qe/A+yyGsbnHqojQW6vGEZQpZ9bf4ir+QaZLRHijgXFmffMA+SRONfdlnxhLJ6/b6ZaMg== -"@hyperledger/aries-askar-nodejs@^0.1.0-dev.1": - version "0.1.0-dev.1" - resolved "https://registry.yarnpkg.com/@hyperledger/aries-askar-nodejs/-/aries-askar-nodejs-0.1.0-dev.1.tgz#b384d422de48f0ce5918e1612d2ca32ebd160520" - integrity sha512-XrRskQ0PaNAerItvfxKkS8YaVg+iuImguoqfyQ4ZSaePKZQnTqZpkxo6faKS+GlsaubRXz/6yz3YndVRIxPO+w== +"@hyperledger/aries-askar-nodejs@^0.1.0-dev.3": + version "0.1.0-dev.3" + resolved "https://registry.yarnpkg.com/@hyperledger/aries-askar-nodejs/-/aries-askar-nodejs-0.1.0-dev.3.tgz#19ecff41f81525efea8212a3ad6b8c3db11950c4" + integrity sha512-9hnCNWxIRkLP793P4DuZAJRWfxf1v6NdQyEgoMdNletcP7KAf/YfBqySTYGqA6TIiMu/abNrmq+WsHkK0yyZ+g== dependencies: - "@hyperledger/aries-askar-shared" "0.1.0-dev.1" + "@hyperledger/aries-askar-shared" "0.1.0-dev.3" "@mapbox/node-pre-gyp" "^1.0.10" ffi-napi "^4.0.3" node-cache "^5.1.2" @@ -889,29 +889,29 @@ ref-napi "^3.0.3" ref-struct-di "^1.1.1" -"@hyperledger/aries-askar-shared@0.1.0-dev.1", "@hyperledger/aries-askar-shared@^0.1.0-dev.1": - version "0.1.0-dev.1" - resolved "https://registry.yarnpkg.com/@hyperledger/aries-askar-shared/-/aries-askar-shared-0.1.0-dev.1.tgz#4e4e494c3a44c7c82f7b95ad4f06149f2a3a9b6c" - integrity sha512-Pt525M6CvnE3N6jxMpSqLy7RpOsc4oqa2Q+hc2UdCHuSYwmM/aeqt6wiA5dpghvl8g/78lCi1Dz74pzp7Dmm3w== +"@hyperledger/aries-askar-shared@0.1.0-dev.3", "@hyperledger/aries-askar-shared@^0.1.0-dev.3": + version "0.1.0-dev.3" + resolved "https://registry.yarnpkg.com/@hyperledger/aries-askar-shared/-/aries-askar-shared-0.1.0-dev.3.tgz#2056db8c0671ec4b1e926e1491fdca9357ede633" + integrity sha512-LIRyCg2PK6wN483Bdzq4eJmQ2LNCCRq2g7GF4yv+H+V04ky7hdeoJbSKN8lYr/OQn1tS6ALx9p2ArvAt7pTfVw== dependencies: fast-text-encoding "^1.0.3" -"@hyperledger/indy-vdr-nodejs@^0.1.0-dev.4": - version "0.1.0-dev.4" - resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.1.0-dev.4.tgz#b5d2090b30c4a51e4e4f15a024054aada0d3550e" - integrity sha512-SwvcoOONhxD9LaX7vunNi1KFKmDb8wmutkBI+Hl6JMX3R+0QgpyQx5M3cfp+V34fBS8pqzKbq9lQmo+pDu3IWg== +"@hyperledger/indy-vdr-nodejs@^0.1.0-dev.6": + version "0.1.0-dev.6" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.1.0-dev.6.tgz#28946107feb6c641839de843cc7ddca9eef01f8d" + integrity sha512-jtFRkfjiveKIfeyTx9qDAUXLtpFaiZlUGN2ts2zX8QvY6XGZEKc6LvhMPzLW5kLW34u6ldEqjG4mjyAawUKRsA== dependencies: - "@hyperledger/indy-vdr-shared" "0.1.0-dev.4" + "@hyperledger/indy-vdr-shared" "0.1.0-dev.6" "@mapbox/node-pre-gyp" "^1.0.10" ffi-napi "^4.0.3" ref-array-di "^1.2.2" ref-napi "^3.0.3" ref-struct-di "^1.1.1" -"@hyperledger/indy-vdr-shared@0.1.0-dev.4", "@hyperledger/indy-vdr-shared@^0.1.0-dev.4": - version "0.1.0-dev.4" - resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.1.0-dev.4.tgz#ad9ff18ea285cf3c8ba0b4a5bff03c02f57898e4" - integrity sha512-M6AnLQNryEqcWiH8oNNI/ovkFOykFg7zlO4oM+1xMbHbNzAe6ShBYQDB189zTQAG4RUkuA8yiLHt90g/q6N8dg== +"@hyperledger/indy-vdr-shared@0.1.0-dev.6", "@hyperledger/indy-vdr-shared@^0.1.0-dev.6": + version "0.1.0-dev.6" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.1.0-dev.6.tgz#345b6ff60d29d74615ee882e225af674315a0bda" + integrity sha512-MIUdm3zIwKfFZmZSwbAPiZHEZE0HhsIPjeEWiu//Z1m9GDDSNhEyxsHuVN17pE0pcxwbuCQrIaK3v4Tc6x0jJw== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0" From eeb89143e6118bdce5077f926c686d580fc4b068 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 14 Feb 2023 23:55:11 +0100 Subject: [PATCH 11/22] style: eslint fixes Signed-off-by: Timo Glastra --- package.json | 2 +- .../v1/errors/V1CredentialProblemReportError.ts | 5 ++++- .../credentials/v1/messages/V1CredentialAckMessage.ts | 4 +++- .../v1/messages/V1CredentialProblemReportMessage.ts | 9 +++------ .../credentials/v1/messages/V1IssueCredentialMessage.ts | 3 ++- .../v1/messages/V1ProposeCredentialMessage.ts | 5 ++++- packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts | 2 +- .../tests/bbs-signing-provider.e2e.test.ts | 2 +- .../decorators/signature/SignatureDecoratorUtils.test.ts | 3 +-- packages/core/src/index.ts | 1 + .../connections/__tests__/ConnectionService.test.ts | 2 +- .../core/src/modules/credentials/CredentialsModule.ts | 2 +- .../modules/vc/__tests__/W3cCredentialService.test.ts | 3 +-- packages/core/tests/helpers.ts | 2 -- packages/indy-sdk/tests/setupIndySdkModule.ts | 2 ++ .../indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts | 2 +- packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts | 2 +- .../src/__tests__/QuestionAnswerService.test.ts | 2 +- tests/InMemoryStorageService.ts | 1 + tests/e2e-askar-indy-sdk-wallet-subject.test.ts | 3 +-- tests/e2e-http.test.ts | 4 +++- tests/e2e-subject.test.ts | 5 +++-- tests/e2e-test.ts | 9 +++++---- tests/e2e-ws-pickup-v2.test.ts | 4 +++- tests/e2e-ws.test.ts | 4 +++- yarn.lock | 9 ++++++++- 26 files changed, 56 insertions(+), 36 deletions(-) diff --git a/package.json b/package.json index 8656ccf5af..e3c92bdeef 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "eslint-plugin-import": "^2.23.4", "eslint-plugin-prettier": "^3.4.0", "express": "^4.17.1", - "indy-sdk": "^1.16.0-dev-1636", + "indy-sdk": "^1.16.0-dev-1655", "jest": "^27.0.4", "lerna": "^4.0.0", "libsodium-wrappers": "^0.7.10", diff --git a/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts b/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts index 6d22aff7bd..113a8ac6f2 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts @@ -1,4 +1,7 @@ -import { ProblemReportErrorOptions, CredentialProblemReportReason, ProblemReportError } from '@aries-framework/core' +import type { ProblemReportErrorOptions, CredentialProblemReportReason } from '@aries-framework/core' + +import { ProblemReportError } from '@aries-framework/core' + import { V1CredentialProblemReportMessage } from '../messages' export interface V1CredentialProblemReportErrorOptions extends ProblemReportErrorOptions { diff --git a/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts index 9a90ce094d..db9bba955d 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialAckMessage.ts @@ -1,4 +1,6 @@ -import { AckMessageOptions, AckMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' +import type { AckMessageOptions } from '@aries-framework/core' + +import { AckMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export type V1CredentialAckMessageOptions = AckMessageOptions diff --git a/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts index 7601303059..7b3a3c4c06 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1CredentialProblemReportMessage.ts @@ -1,9 +1,6 @@ -import { - ProblemReportMessageOptions, - ProblemReportMessage, - IsValidMessageType, - parseMessageType, -} from '@aries-framework/core' +import type { ProblemReportMessageOptions } from '@aries-framework/core' + +import { ProblemReportMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' export type V1CredentialProblemReportMessageOptions = ProblemReportMessageOptions diff --git a/packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts index 00255991a6..a8727a355a 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1IssueCredentialMessage.ts @@ -1,7 +1,8 @@ +import type { AnonCredsCredential } from '../../../../models' + import { Attachment, AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsString, IsOptional, IsArray, ValidateNested, IsInstance } from 'class-validator' -import { AnonCredsCredential } from '../../../../models' export const INDY_CREDENTIAL_ATTACHMENT_ID = 'libindy-cred-0' diff --git a/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts index a36db35b37..7c50693cad 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts @@ -1,13 +1,16 @@ -import { AgentMessage, Attachment, IsValidMessageType, parseMessageType } from '@aries-framework/core' +import type { Attachment } from '@aries-framework/core' +import { AgentMessage, IsValidMessageType, parseMessageType } from '@aries-framework/core' import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' + import { legacyIndyCredentialDefinitionIdRegex, legacyIndyDidRegex, legacyIndySchemaIdRegex, legacyIndySchemaVersionRegex, } from '../../../../utils' + import { V1CredentialPreview } from './V1CredentialPreview' export interface V1ProposeCredentialMessageOptions { diff --git a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index 74216611e6..40845e3255 100644 --- a/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts @@ -19,13 +19,13 @@ import { W3cVerifiablePresentation, Ed25519Signature2018, } from '@aries-framework/core' -import indySdk from 'indy-sdk' import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuiteRegistry' import { W3cVcModuleConfig } from '../../core/src/modules/vc/W3cVcModuleConfig' import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' import { IndySdkWallet } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { BbsBlsSignature2020, BbsBlsSignatureProof2020, Bls12381g2SigningProvider } from '../src' import { BbsBlsSignature2020Fixtures } from './fixtures' diff --git a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts index 205598c7a8..9f5666483d 100644 --- a/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts +++ b/packages/bbs-signatures/tests/bbs-signing-provider.e2e.test.ts @@ -8,10 +8,10 @@ import { SigningProviderRegistry, } from '@aries-framework/core' import { BBS_SIGNATURE_LENGTH } from '@mattrglobal/bbs-signatures' -import indySdk from 'indy-sdk' import testLogger from '../../core/tests/logger' import { IndySdkWallet } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { Bls12381g2SigningProvider } from '../src' import { describeSkipNode17And18 } from './util' diff --git a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts index 4fd501ee3a..a4358d5bfa 100644 --- a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts +++ b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts @@ -1,8 +1,7 @@ import type { Wallet } from '../../wallet' -import indySdk from 'indy-sdk' - import { IndySdkWallet } from '../../../../indy-sdk/src' +import { indySdk } from '../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig } from '../../../tests/helpers' import { KeyType } from '../../crypto' import { SigningProviderRegistry } from '../../crypto/signing-provider' diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 10fa76550e..f891909c09 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -75,6 +75,7 @@ export { MessageValidator } from './utils/MessageValidator' export { LinkedAttachment, LinkedAttachmentOptions } from './utils/LinkedAttachment' import { parseInvitationUrl } from './utils/parseInvitation' import { uuid } from './utils/uuid' + const utils = { uuid, parseInvitationUrl, diff --git a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 534dcd97e2..c07c94a893 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -3,10 +3,10 @@ import type { Wallet } from '../../../wallet/Wallet' import type { DidDocument } from '../../dids' import type { Routing } from '../services/ConnectionService' -import indySdk from 'indy-sdk' import { Subject } from 'rxjs' import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig, getAgentContext, diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 482c688d92..1a09ff1b0f 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,5 +1,6 @@ import type { CredentialsModuleConfigOptions } from './CredentialsModuleConfig' import type { CredentialProtocol } from './protocol/CredentialProtocol' +import type { V2CredentialProtocol } from './protocol/v2' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { ApiModule, DependencyManager } from '../../plugins' import type { Constructor } from '../../utils/mixins' @@ -10,7 +11,6 @@ import { Protocol } from '../../agent/models' import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' import { RevocationNotificationService } from './protocol/revocation-notification/services' -import { V2CredentialProtocol } from './protocol/v2' import { CredentialRepository } from './repository' /** diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 04d4b644bd..c29aa0138f 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -1,9 +1,8 @@ import type { AgentContext } from '../../../agent' import type { Wallet } from '../../../wallet' -import indySdk from 'indy-sdk' - import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig, getAgentContext, mockFunction } from '../../../../tests/helpers' import { KeyType } from '../../../crypto' import { SigningProviderRegistry } from '../../../crypto/signing-provider' diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 752cd14f85..bfc0fe1387 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -171,8 +171,6 @@ const isTrustPingReceivedEvent = (e: BaseEvent): e is TrustPingReceivedEvent => e.type === TrustPingEventTypes.TrustPingReceivedEvent const isTrustPingResponseReceivedEvent = (e: BaseEvent): e is TrustPingResponseReceivedEvent => e.type === TrustPingEventTypes.TrustPingResponseReceivedEvent -const isBasicMessageStateChangedEvent = (e: BaseEvent): e is BasicMessageStateChangedEvent => - e.type === BasicMessageEventTypes.BasicMessageStateChanged export function waitForProofExchangeRecordSubject( subject: ReplaySubject | Observable, diff --git a/packages/indy-sdk/tests/setupIndySdkModule.ts b/packages/indy-sdk/tests/setupIndySdkModule.ts index dad0fafd47..2a61b4b1e3 100644 --- a/packages/indy-sdk/tests/setupIndySdkModule.ts +++ b/packages/indy-sdk/tests/setupIndySdkModule.ts @@ -4,6 +4,8 @@ import indySdk from 'indy-sdk' import { genesisPath, taaVersion, taaAcceptanceMechanism } from '../../core/tests/helpers' import { IndySdkModule, IndySdkSovDidRegistrar, IndySdkSovDidResolver } from '../src' +export { indySdk } + export const getIndySdkModules = ({ indyNamespace = `localhost-${utils.uuid()}`, }: { indyNamespace?: string } = {}) => ({ diff --git a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts index 105f31f5c2..ae7d081322 100644 --- a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts @@ -7,12 +7,12 @@ import { KeyType, SigningProviderRegistry, } from '@aries-framework/core' -import indySdk from 'indy-sdk' import { parseDid } from '../../core/src/modules/dids/domain/parse' import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' import testLogger from '../../core/tests/logger' import { IndySdkWallet } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { IndyVdrSovDidResolver } from '../src/dids' import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' diff --git a/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts index a98d12e5cc..34f1b0bda0 100644 --- a/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-pool.e2e.test.ts @@ -2,11 +2,11 @@ import type { Key } from '@aries-framework/core' import { KeyType, SigningProviderRegistry } from '@aries-framework/core' import { GetNymRequest, NymRequest, SchemaRequest, CredentialDefinitionRequest } from '@hyperledger/indy-vdr-shared' -import indySdk from 'indy-sdk' import { genesisTransactions, getAgentConfig, getAgentContext } from '../../core/tests/helpers' import testLogger from '../../core/tests/logger' import { IndySdkWallet } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { IndyVdrPool } from '../src/pool' import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' diff --git a/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts index 881d1b4e21..4a21491d36 100644 --- a/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts +++ b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts @@ -3,11 +3,11 @@ import type { QuestionAnswerStateChangedEvent, ValidResponse } from '@aries-fram import { EventEmitter, SigningProviderRegistry, InboundMessageContext, DidExchangeState } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' -import indySdk from 'indy-sdk' import { Subject } from 'rxjs' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../core/tests/helpers' import { IndySdkWallet } from '../../../indy-sdk/src' +import { indySdk } from '../../../indy-sdk/tests/setupIndySdkModule' import { QuestionAnswerRecord, diff --git a/tests/InMemoryStorageService.ts b/tests/InMemoryStorageService.ts index 0e1814c3fb..339080b404 100644 --- a/tests/InMemoryStorageService.ts +++ b/tests/InMemoryStorageService.ts @@ -18,6 +18,7 @@ interface StorageRecord { } @injectable() +// eslint-disable-next-line @typescript-eslint/no-explicit-any export class InMemoryStorageService = BaseRecord> implements StorageService { diff --git a/tests/e2e-askar-indy-sdk-wallet-subject.test.ts b/tests/e2e-askar-indy-sdk-wallet-subject.test.ts index 7c4b3e57f3..199d57a314 100644 --- a/tests/e2e-askar-indy-sdk-wallet-subject.test.ts +++ b/tests/e2e-askar-indy-sdk-wallet-subject.test.ts @@ -1,5 +1,6 @@ import type { SubjectMessage } from './transport/SubjectInboundTransport' +import indySdk from 'indy-sdk' import { Subject } from 'rxjs' import { getAgentOptions, makeConnection, waitForBasicMessage } from '../packages/core/tests/helpers' @@ -9,8 +10,6 @@ import { Agent, DependencyManager, InjectionSymbols } from '@aries-framework/cor import { IndySdkModule, IndySdkStorageService, IndySdkWallet } from '@aries-framework/indy-sdk' import { SubjectInboundTransport } from './transport/SubjectInboundTransport' -import indySdk from 'indy-sdk' - import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' // FIXME: Re-include in tests when Askar NodeJS wrapper performance is improved diff --git a/tests/e2e-http.test.ts b/tests/e2e-http.test.ts index a3b529e8e3..69c4c8abf2 100644 --- a/tests/e2e-http.test.ts +++ b/tests/e2e-http.test.ts @@ -1,10 +1,12 @@ +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' + +import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { HttpOutboundTransport, Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { HttpInboundTransport } from '@aries-framework/node' -import { AnonCredsTestsAgent, getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' const modules = getLegacyAnonCredsModules({ autoAcceptCredentials: AutoAcceptCredential.ContentApproved, diff --git a/tests/e2e-subject.test.ts b/tests/e2e-subject.test.ts index f377bab66b..8d8ae17eca 100644 --- a/tests/e2e-subject.test.ts +++ b/tests/e2e-subject.test.ts @@ -1,16 +1,17 @@ import type { SubjectMessage } from './transport/SubjectInboundTransport' +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { Subject } from 'rxjs' +import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' -import { SubjectInboundTransport } from './transport/SubjectInboundTransport' import { Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' +import { SubjectInboundTransport } from './transport/SubjectInboundTransport' import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' -import { AnonCredsTestsAgent, getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' const modules = getLegacyAnonCredsModules({ autoAcceptCredentials: AutoAcceptCredential.ContentApproved, diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index 6d28388904..46f31e1650 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -1,17 +1,18 @@ +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import type { BaseEvent } from '@aries-framework/core' -import { sleep } from '../packages/core/src/utils/sleep' +import { ReplaySubject } from 'rxjs' + +import { V1CredentialPreview } from '../packages/anoncreds/src/protocols/credentials/v1' import { issueLegacyAnonCredsCredential, presentLegacyAnonCredsProof, prepareForAnonCredsIssuance, - AnonCredsTestsAgent, } from '../packages/anoncreds/tests/legacyAnonCredsSetup' +import { sleep } from '../packages/core/src/utils/sleep' import { makeConnection } from '../packages/core/tests/helpers' import { CredentialState, MediationState, ProofState } from '@aries-framework/core' -import { ReplaySubject } from 'rxjs' -import { V1CredentialPreview } from '../packages/anoncreds/src/protocols/credentials/v1' export async function e2eTest({ mediatorAgent, diff --git a/tests/e2e-ws-pickup-v2.test.ts b/tests/e2e-ws-pickup-v2.test.ts index bca7afff86..0fcd01b5a7 100644 --- a/tests/e2e-ws-pickup-v2.test.ts +++ b/tests/e2e-ws-pickup-v2.test.ts @@ -1,10 +1,12 @@ +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' + +import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -import { AnonCredsTestsAgent, getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' const modules = getLegacyAnonCredsModules({ autoAcceptCredentials: AutoAcceptCredential.ContentApproved, diff --git a/tests/e2e-ws.test.ts b/tests/e2e-ws.test.ts index 6f375270f0..d7e1f76cdd 100644 --- a/tests/e2e-ws.test.ts +++ b/tests/e2e-ws.test.ts @@ -1,10 +1,12 @@ +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' + +import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' import { e2eTest } from './e2e-test' import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -import { AnonCredsTestsAgent, getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' const modules = getLegacyAnonCredsModules({ autoAcceptCredentials: AutoAcceptCredential.ContentApproved, diff --git a/yarn.lock b/yarn.lock index 468c9592e0..0df13402fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2541,6 +2541,13 @@ dependencies: buffer "^6.0.0" +"@types/indy-sdk@^1.16.25": + version "1.16.25" + resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.25.tgz#003eaed57efaae188a5a1ea57aa9bb001ffc3127" + integrity sha512-yAwdEcD013U2HPLaFFkpw+VlOlEQNPCRbY9qcXQwRarSa+ngYKpnu66gDhmWsr34q0bfKDmUVsFqIdGB6KYxIA== + dependencies: + buffer "^6.0.0" + "@types/inquirer@^8.1.3": version "8.2.5" resolved "https://registry.yarnpkg.com/@types/inquirer/-/inquirer-8.2.5.tgz#c508423bcc11126db278170ab07347783ac2300c" @@ -6206,7 +6213,7 @@ indy-sdk-react-native@^0.3.0: dependencies: buffer "^6.0.2" -indy-sdk@^1.16.0-dev-1636: +indy-sdk@^1.16.0-dev-1636, indy-sdk@^1.16.0-dev-1655: version "1.16.0-dev-1655" resolved "https://registry.yarnpkg.com/indy-sdk/-/indy-sdk-1.16.0-dev-1655.tgz#098c38df4a6eb4e13f89c0b86ebe9636944b71e0" integrity sha512-MSWRY8rdnGAegs4v4AnzE6CT9O/3JBMUiE45I0Ihj2DMuH+XS1EJZUQEJsyis6aOQzRavv/xVtaBC8o+6azKuw== From f81f62319ecebb45e50d8ddcaf052e6d2682bcb3 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 14 Feb 2023 23:58:18 +0100 Subject: [PATCH 12/22] style: eslint fixes Signed-off-by: Timo Glastra --- demo/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/demo/package.json b/demo/package.json index b4200b311f..223182457b 100644 --- a/demo/package.json +++ b/demo/package.json @@ -22,6 +22,7 @@ "@aries-framework/indy-vdr": "*", "@aries-framework/node": "*", "@types/figlet": "^1.5.4", + "@types/indy-sdk": "^1.16.25", "@types/inquirer": "^8.1.3", "clear": "^0.1.0", "commander": "^8.3.0", From 4104234fe72056732064f705a5506674ffe64485 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 14 Feb 2023 23:58:51 +0100 Subject: [PATCH 13/22] style: eslint fixes Signed-off-by: Timo Glastra --- packages/anoncreds/package.json | 1 + packages/core/src/crypto/__tests__/JwsService.test.ts | 3 +-- .../v2.ldproof.credentials.propose-offerED25519.test.ts | 2 +- packages/core/src/modules/dids/__tests__/peer-did.test.ts | 2 +- packages/core/tests/jsonld.ts | 3 +-- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/anoncreds/package.json b/packages/anoncreds/package.json index 27ddffa7d6..7f0dfd3bc2 100644 --- a/packages/anoncreds/package.json +++ b/packages/anoncreds/package.json @@ -33,6 +33,7 @@ "devDependencies": { "indy-sdk": "^1.16.0-dev-1636", "rimraf": "^4.0.7", + "rxjs": "^7.8.0", "typescript": "~4.9.4" } } diff --git a/packages/core/src/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index 05643c9332..89e055464e 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,9 +1,8 @@ import type { AgentContext } from '../../agent' import type { Key, Wallet } from '@aries-framework/core' -import indySdk from 'indy-sdk' - import { IndySdkWallet } from '../../../../indy-sdk/src' +import { indySdk } from '../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig, getAgentContext } from '../../../tests/helpers' import { DidKey } from '../../modules/dids' import { Buffer, JsonEncoder } from '../../utils' diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index a9f8a23d1c..b97b0d839c 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -1,7 +1,6 @@ import type { EventReplaySubject } from '../../../../../../tests' import { randomUUID } from 'crypto' -import indySdk from 'indy-sdk' import { LegacyIndyCredentialFormatService, @@ -17,6 +16,7 @@ import { IndySdkSovDidRegistrar, IndySdkSovDidResolver, } from '../../../../../../../indy-sdk/src' +import { indySdk } from '../../../../../../../indy-sdk/tests/setupIndySdkModule' import { setupEventReplaySubjects, setupSubjectTransports, diff --git a/packages/core/src/modules/dids/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index 406c3b6d74..d9456f7fe5 100644 --- a/packages/core/src/modules/dids/__tests__/peer-did.test.ts +++ b/packages/core/src/modules/dids/__tests__/peer-did.test.ts @@ -1,11 +1,11 @@ import type { AgentContext } from '../../../agent' import type { Wallet } from '../../../wallet' -import indySdk from 'indy-sdk' import { Subject } from 'rxjs' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentConfig, getAgentContext } from '../../../../tests/helpers' import { EventEmitter } from '../../../agent/EventEmitter' import { InjectionSymbols } from '../../../constants' diff --git a/packages/core/tests/jsonld.ts b/packages/core/tests/jsonld.ts index aa08dc84e6..ace2fe5dac 100644 --- a/packages/core/tests/jsonld.ts +++ b/packages/core/tests/jsonld.ts @@ -1,10 +1,9 @@ import type { EventReplaySubject } from './events' import type { AutoAcceptCredential, AutoAcceptProof, ConnectionRecord } from '../src' -import indySdk from 'indy-sdk' - import { BbsModule } from '../../bbs-signatures/src/BbsModule' import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { CacheModule, CredentialEventTypes, From 12743dfe745cabc02f441ad85420afed0c05ffa1 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 16 Feb 2023 00:50:58 +0100 Subject: [PATCH 14/22] fix: quite some tests Signed-off-by: Timo Glastra --- .../action-menu/tests/action-menu.e2e.test.ts | 48 +++++++------------ packages/askar/src/wallet/AskarWallet.ts | 5 +- ...proof.credentials.propose-offerBbs.test.ts | 5 +- .../src/modules/cache/InMemoryLruCache.ts | 4 ++ .../jsonld/JsonLdCredentialFormatService.ts | 2 +- packages/core/tests/index.ts | 1 + packages/core/tests/indySdk.ts | 3 ++ .../src/dids/IndySdkSovDidRegistrar.ts | 3 -- .../__tests__/IndySdkSovDidRegistrar.test.ts | 22 ++++++--- .../__tests__/IndySdkPoolService.test.ts | 7 ++- packages/indy-sdk/tests/setupIndySdkModule.ts | 7 ++- .../indy-vdr-anoncreds-registry.e2e.test.ts | 33 ++++++++----- .../tests/indy-vdr-did-registrar.e2e.test.ts | 15 +++--- .../tests/openid4vc-client.e2e.test.ts | 29 ++++++----- .../tests/question-answer.e2e.test.ts | 46 ++++++------------ .../tenants/src/__tests__/TenantAgent.test.ts | 5 ++ .../tenants/tests/tenant-sessions.e2e.test.ts | 4 +- packages/tenants/tests/tenants.e2e.test.ts | 5 +- tests/e2e-test.ts | 18 ++++--- 19 files changed, 133 insertions(+), 129 deletions(-) create mode 100644 packages/core/tests/indySdk.ts diff --git a/packages/action-menu/tests/action-menu.e2e.test.ts b/packages/action-menu/tests/action-menu.e2e.test.ts index 553d7e0c20..8ba99acdbc 100644 --- a/packages/action-menu/tests/action-menu.e2e.test.ts +++ b/packages/action-menu/tests/action-menu.e2e.test.ts @@ -1,13 +1,9 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '@aries-framework/core' import { Agent } from '@aries-framework/core' -import { Subject } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, makeConnection } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' +import { getAgentOptions, makeConnection, testLogger, setupSubjectTransports, indySdk } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { ActionMenu, @@ -19,14 +15,19 @@ import { import { waitForActionMenuRecord } from './helpers' +const modules = { + actionMenu: new ActionMenuModule(), + indySdk: new IndySdkModule({ + indySdk, + }), +} + const faberAgentOptions = getAgentOptions( 'Faber Action Menu', { endpoints: ['rxjs:faber'], }, - { - actionMenu: new ActionMenuModule(), - } + modules ) const aliceAgentOptions = getAgentOptions( @@ -34,18 +35,12 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - { - actionMenu: new ActionMenuModule(), - } + modules ) describe('Action Menu', () => { - let faberAgent: Agent<{ - actionMenu: ActionMenuModule - }> - let aliceAgent: Agent<{ - actionMenu: ActionMenuModule - }> + let faberAgent: Agent + let aliceAgent: Agent let faberConnection: ConnectionRecord let aliceConnection: ConnectionRecord @@ -84,21 +79,12 @@ describe('Action Menu', () => { }) beforeEach(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + setupSubjectTransports([faberAgent, aliceAgent]) + + await faberAgent.initialize() await aliceAgent.initialize() ;[aliceConnection, faberConnection] = await makeConnection(aliceAgent, faberAgent) }) diff --git a/packages/askar/src/wallet/AskarWallet.ts b/packages/askar/src/wallet/AskarWallet.ts index d2a970d447..c84370cadf 100644 --- a/packages/askar/src/wallet/AskarWallet.ts +++ b/packages/askar/src/wallet/AskarWallet.ts @@ -361,10 +361,7 @@ export class AskarWallet implements Wallet { const algorithm = keyAlgFromString(keyType) // Create key from seed - const key = seed - ? // FIXME: we call fromSecretBytes using the secretKey - AskarKey.fromSecretBytes({ secretKey: Buffer.from(seed), algorithm }) - : AskarKey.generate(algorithm) + const key = seed ? AskarKey.fromSeed({ seed: Buffer.from(seed), algorithm }) : AskarKey.generate(algorithm) // Store key await this.session.insertKey({ key, name: encodeToBase58(key.publicBytes) }) diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index 195dbac91d..e1e4663d3e 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -112,7 +112,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { state: CredentialState.OfferReceived, }) - const offerMessage = await faberAgent.credentials.findOfferMessage(aliceCredentialRecord.id) + const offerMessage = await faberAgent.credentials.findOfferMessage(faberCredentialRecord.id) expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', '@id': expect.any(String), @@ -201,8 +201,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { const w3cCredential = (credentialMessage as V2IssueCredentialMessage).credentialAttachments[0].getDataAsJson() expect(w3cCredential).toMatchObject({ - todo: 'todo', - context: [ + '@context': [ 'https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', 'https://w3id.org/security/bbs/v1', diff --git a/packages/core/src/modules/cache/InMemoryLruCache.ts b/packages/core/src/modules/cache/InMemoryLruCache.ts index 4a56cb97c5..4f6ba0733e 100644 --- a/packages/core/src/modules/cache/InMemoryLruCache.ts +++ b/packages/core/src/modules/cache/InMemoryLruCache.ts @@ -51,6 +51,10 @@ export class InMemoryLruCache implements Cache { }) } + public clear() { + this.cache.clear() + } + public async remove(agentContext: AgentContext, key: string): Promise { this.removeExpiredItems() this.cache.delete(key) diff --git a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts index 52be8f6493..0b49134c0f 100644 --- a/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/jsonld/JsonLdCredentialFormatService.ts @@ -252,7 +252,7 @@ export class JsonLdCredentialFormatService implements CredentialFormatService() const eventEmitter = new EventEmitter(agentDependencies, new Subject()) const didRepository = new DidRepository(storageService, eventEmitter) +const createDidMock = jest.fn(async () => ['R1xKJw17sUoXhejEpugMYJ', 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu']) +mockProperty(wallet, 'handle', 10) + const agentContext = getAgentContext({ wallet, registerInstances: [ [DidRepository, didRepository], [IndySdkPoolService, indySdkPoolServiceMock], - [IndySdkSymbol, indySdk], + [IndySdkSymbol, { createAndStoreMyDid: createDidMock }], ], agentConfig, }) @@ -78,6 +81,11 @@ describe('IndySdkSovDidRegistrar', () => { const agentContext = getAgentContext({ wallet: {} as unknown as Wallet, agentConfig, + registerInstances: [ + [DidRepository, didRepository], + [IndySdkPoolService, indySdkPoolServiceMock], + [IndySdkSymbol, indySdk], + ], }) const result = await indySdkSovDidRegistrar.create(agentContext, { @@ -97,7 +105,7 @@ describe('IndySdkSovDidRegistrar', () => { didRegistrationMetadata: {}, didState: { state: 'failed', - reason: 'unknownError: Expected wallet to be instance of IndyWallet, found Object', + reason: 'unknownError: Expected wallet to be instance of IndySdkWallet, found Object', }, }) }) @@ -150,7 +158,7 @@ describe('IndySdkSovDidRegistrar', () => { // Alias 'Hello', // Pool - { config: { id: 'pool1', indyNamespace: 'pool1' } }, + { config: { indyNamespace: 'pool1' } }, // Role 'STEWARD' ) @@ -233,7 +241,7 @@ describe('IndySdkSovDidRegistrar', () => { // Alias 'Hello', // Pool - { config: { id: 'pool1', indyNamespace: 'pool1' } }, + { config: { indyNamespace: 'pool1' } }, // Role 'STEWARD' ) @@ -333,9 +341,9 @@ describe('IndySdkSovDidRegistrar', () => { }) expect(saveCalled).toHaveBeenCalledTimes(1) - const [, didRecord] = saveCalled.mock.calls[0] + const [saveEvent] = saveCalled.mock.calls[0] - expect(didRecord).toMatchObject({ + expect(saveEvent.payload.record).toMatchObject({ did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', role: DidDocumentRole.Created, _tags: { diff --git a/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts index 8256200d76..c8adaaedcf 100644 --- a/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts +++ b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts @@ -53,7 +53,8 @@ const pools: IndySdkPoolConfig[] = [ ] const config = getAgentConfig('IndySdkPoolServiceTest') -const cache = new InMemoryLruCache({ limit: 200 }) +const cache = new InMemoryLruCache({ limit: 1 }) + const indySdkModule = new IndySdkModuleConfig({ indySdk, networks: pools }) const wallet = new IndySdkWallet(indySdk, config.logger, new SigningProviderRegistry([])) @@ -80,6 +81,10 @@ describe('IndySdkPoolService', () => { await wallet.delete() }) + afterEach(() => { + cache.clear() + }) + describe('getPoolForDid', () => { it('should throw a IndySdkPoolNotConfiguredError error if no pools are configured on the pool service', async () => { const oldPools = poolService.pools diff --git a/packages/indy-sdk/tests/setupIndySdkModule.ts b/packages/indy-sdk/tests/setupIndySdkModule.ts index 2a61b4b1e3..949a08bccf 100644 --- a/packages/indy-sdk/tests/setupIndySdkModule.ts +++ b/packages/indy-sdk/tests/setupIndySdkModule.ts @@ -6,16 +6,15 @@ import { IndySdkModule, IndySdkSovDidRegistrar, IndySdkSovDidResolver } from '.. export { indySdk } -export const getIndySdkModules = ({ - indyNamespace = `localhost-${utils.uuid()}`, -}: { indyNamespace?: string } = {}) => ({ +export const getIndySdkModules = () => ({ indySdk: new IndySdkModule({ indySdk, networks: [ { + id: `localhost-${utils.uuid()}`, isProduction: false, genesisPath, - indyNamespace, + indyNamespace: 'localhost', transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, }, ], diff --git a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts index 88701769bb..08874bb3a9 100644 --- a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts @@ -1,6 +1,9 @@ -import { Agent } from '@aries-framework/core' +import { Agent, DidsModule } from '@aries-framework/core' import { agentDependencies, getAgentConfig } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrSovDidResolver } from '../src' import { IndyVdrAnonCredsRegistry } from '../src/anoncreds/IndyVdrAnonCredsRegistry' import { IndyVdrPoolService } from '../src/pool' @@ -16,6 +19,14 @@ const indyVdrAnonCredsRegistry = new IndyVdrAnonCredsRegistry() const agent = new Agent({ config: agentConfig, dependencies: agentDependencies, + modules: { + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + resolvers: [new IndyVdrSovDidResolver()], + }), + }, }) agent.dependencyManager.registerInstance(IndyVdrPoolService, indyVdrPoolService) @@ -159,18 +170,16 @@ describe('IndyVdrAnonCredsRegistry', () => { type: 'CL', value: { primary: { - primary: { - n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', - r: { - age: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', - master_secret: - '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', - }, - rctxt: - '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', - s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', - z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', + n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', + r: { + age: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', + master_secret: + '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', }, + rctxt: + '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', + s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', + z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', }, }, }, diff --git a/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts index 6b24cc5c82..e8306331ee 100644 --- a/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts @@ -10,14 +10,15 @@ import { DidCommV1Service, DidCommV2Service, DidDocumentService, - IndyWallet, } from '@aries-framework/core' import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' import { Subject } from 'rxjs' -import { IndyStorageService } from '../../core/src/storage/IndyStorageService' +import { InMemoryStorageService } from '../../../tests/InMemoryStorageService' import { agentDependencies, getAgentConfig, getAgentContext } from '../../core/tests/helpers' import testLogger from '../../core/tests/logger' +import { IndySdkWallet } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { IndyVdrIndyDidRegistrar } from '../src/dids/IndyVdrIndyDidRegistrar' import { IndyVdrIndyDidResolver } from '../src/dids/IndyVdrIndyDidResolver' import { indyDidFromNamespaceAndInitialKey } from '../src/dids/didIndyUtil' @@ -27,7 +28,7 @@ import { DID_INDY_REGEX } from '../src/utils/did' import { indyVdrModuleConfig } from './helpers' const logger = testLogger -const wallet = new IndyWallet(agentDependencies, logger, new SigningProviderRegistry([])) +const wallet = new IndySdkWallet(indySdk, logger, new SigningProviderRegistry([])) const agentConfig = getAgentConfig('IndyVdrIndyDidRegistrar E2E', { logger }) @@ -43,7 +44,7 @@ const agentContext = getAgentContext({ registerInstances: [ [InjectionSymbols.Stop$, new Subject()], [InjectionSymbols.AgentDependencies, agentDependencies], - [InjectionSymbols.StorageService, new IndyStorageService(agentDependencies)], + [InjectionSymbols.StorageService, new InMemoryStorageService()], [IndyVdrPoolService, new IndyVdrPoolService(logger, indyVdrModuleConfig)], [CacheModuleConfig, new CacheModuleConfig({ cache })], ], @@ -53,11 +54,7 @@ const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolSer describe('Indy VDR registrar E2E', () => { beforeAll(async () => { - await indyVdrPoolService.connectToPools() - - if (agentConfig.walletConfig) { - await wallet.createAndOpen(agentConfig.walletConfig) - } + await wallet.createAndOpen(agentConfig.walletConfig) signerKey = await wallet.createKey({ seed: '000000000000000000000000Trustee9', diff --git a/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts b/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts index 8b01c14c17..7201ad3450 100644 --- a/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts +++ b/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts @@ -5,29 +5,28 @@ import nock, { cleanAll, enableNetConnect } from 'nock' import { didKeyToInstanceOfKey } from '../../core/src/modules/dids/helpers' import { customDocumentLoader } from '../../core/src/modules/vc/__tests__/documentLoader' -import { getAgentOptions } from '../../core/tests/helpers' +import { getAgentOptions, indySdk } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { OpenId4VcClientModule } from '@aries-framework/openid4vc-client' import { acquireAccessTokenResponse, credentialRequestResponse, getMetadataResponse } from './fixtures' +const modules = { + openId4VcClient: new OpenId4VcClientModule(), + w3cVc: new W3cVcModule({ + documentLoader: customDocumentLoader, + }), + indySdk: new IndySdkModule({ + indySdk, + }), +} + describe('OpenId4VcClient', () => { - let agent: Agent<{ - openId4VcClient: OpenId4VcClientModule - w3cVc: W3cVcModule - }> + let agent: Agent beforeEach(async () => { - const agentOptions = getAgentOptions( - 'OpenId4VcClient Agent', - {}, - { - openId4VcClient: new OpenId4VcClientModule(), - w3cVc: new W3cVcModule({ - documentLoader: customDocumentLoader, - }), - } - ) + const agentOptions = getAgentOptions('OpenId4VcClient Agent', {}, modules) agent = new Agent(agentOptions) await agent.initialize() diff --git a/packages/question-answer/tests/question-answer.e2e.test.ts b/packages/question-answer/tests/question-answer.e2e.test.ts index c2c35d8c2b..b15d71efe6 100644 --- a/packages/question-answer/tests/question-answer.e2e.test.ts +++ b/packages/question-answer/tests/question-answer.e2e.test.ts @@ -1,26 +1,27 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '@aries-framework/core' import { Agent } from '@aries-framework/core' -import { Subject } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { getAgentOptions, makeConnection } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' +import { indySdk, setupSubjectTransports, testLogger, getAgentOptions, makeConnection } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { QuestionAnswerModule, QuestionAnswerRole, QuestionAnswerState } from '@aries-framework/question-answer' import { waitForQuestionAnswerRecord } from './helpers' +const modules = { + questionAnswer: new QuestionAnswerModule(), + indySdk: new IndySdkModule({ + indySdk, + }), +} + const bobAgentOptions = getAgentOptions( 'Bob Question Answer', { endpoints: ['rxjs:bob'], }, - { - questionAnswer: new QuestionAnswerModule(), - } + modules ) const aliceAgentOptions = getAgentOptions( @@ -28,37 +29,20 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - { - questionAnswer: new QuestionAnswerModule(), - } + modules ) describe('Question Answer', () => { - let bobAgent: Agent<{ - questionAnswer: QuestionAnswerModule - }> - let aliceAgent: Agent<{ - questionAnswer: QuestionAnswerModule - }> + let bobAgent: Agent + let aliceAgent: Agent let aliceConnection: ConnectionRecord beforeEach(async () => { - const bobMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:bob': bobMessages, - 'rxjs:alice': aliceMessages, - } - bobAgent = new Agent(bobAgentOptions) - bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) - bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await bobAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) + setupSubjectTransports([bobAgent, aliceAgent]) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await bobAgent.initialize() await aliceAgent.initialize() ;[aliceConnection] = await makeConnection(aliceAgent, bobAgent) }) diff --git a/packages/tenants/src/__tests__/TenantAgent.test.ts b/packages/tenants/src/__tests__/TenantAgent.test.ts index ce599ef4bf..1c3bb05cc3 100644 --- a/packages/tenants/src/__tests__/TenantAgent.test.ts +++ b/packages/tenants/src/__tests__/TenantAgent.test.ts @@ -1,6 +1,8 @@ import { Agent, AgentContext } from '@aries-framework/core' +import { indySdk } from '../../../core/tests' import { agentDependencies, getAgentConfig, getAgentContext } from '../../../core/tests/helpers' +import { IndySdkModule } from '../../../indy-sdk/src' import { TenantAgent } from '../TenantAgent' describe('TenantAgent', () => { @@ -14,6 +16,9 @@ describe('TenantAgent', () => { }, }, dependencies: agentDependencies, + modules: { + indySdk: new IndySdkModule({ indySdk }), + }, }) const tenantDependencyManager = agent.dependencyManager.createChild() diff --git a/packages/tenants/tests/tenant-sessions.e2e.test.ts b/packages/tenants/tests/tenant-sessions.e2e.test.ts index c1e0a212b1..19a99eda19 100644 --- a/packages/tenants/tests/tenant-sessions.e2e.test.ts +++ b/packages/tenants/tests/tenant-sessions.e2e.test.ts @@ -3,7 +3,8 @@ import type { InitConfig } from '@aries-framework/core' import { Agent } from '@aries-framework/core' import { agentDependencies } from '@aries-framework/node' -import testLogger from '../../core/tests/logger' +import { testLogger, indySdk } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { TenantsModule } from '@aries-framework/tenants' @@ -24,6 +25,7 @@ const agent = new Agent({ dependencies: agentDependencies, modules: { tenants: new TenantsModule({ sessionAcquireTimeout: 10000 }), + indySdk: new IndySdkModule({ indySdk }), }, }) diff --git a/packages/tenants/tests/tenants.e2e.test.ts b/packages/tenants/tests/tenants.e2e.test.ts index 42b5b599df..7f955b395e 100644 --- a/packages/tenants/tests/tenants.e2e.test.ts +++ b/packages/tenants/tests/tenants.e2e.test.ts @@ -5,7 +5,8 @@ import { agentDependencies } from '@aries-framework/node' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import testLogger from '../../core/tests/logger' +import { testLogger, indySdk } from '../../core/tests' +import { IndySdkModule } from '../../indy-sdk/src' import { TenantsModule } from '@aries-framework/tenants' @@ -36,6 +37,7 @@ const agent1 = new Agent({ config: agent1Config, modules: { tenants: new TenantsModule(), + indySdk: new IndySdkModule({ indySdk }), }, dependencies: agentDependencies, }) @@ -44,6 +46,7 @@ const agent2 = new Agent({ config: agent2Config, modules: { tenants: new TenantsModule(), + indySdk: new IndySdkModule({ indySdk }), }, dependencies: agentDependencies, }) diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index 46f31e1650..b1bee59014 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -1,7 +1,4 @@ import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' -import type { BaseEvent } from '@aries-framework/core' - -import { ReplaySubject } from 'rxjs' import { V1CredentialPreview } from '../packages/anoncreds/src/protocols/credentials/v1' import { @@ -10,9 +7,16 @@ import { prepareForAnonCredsIssuance, } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { sleep } from '../packages/core/src/utils/sleep' +import { setupEventReplaySubjects } from '../packages/core/tests' import { makeConnection } from '../packages/core/tests/helpers' -import { CredentialState, MediationState, ProofState } from '@aries-framework/core' +import { + CredentialState, + MediationState, + ProofState, + CredentialEventTypes, + ProofEventTypes, +} from '@aries-framework/core' export async function e2eTest({ mediatorAgent, @@ -23,8 +27,10 @@ export async function e2eTest({ recipientAgent: AnonCredsTestsAgent senderAgent: AnonCredsTestsAgent }) { - const senderReplay = new ReplaySubject() - const recipientReplay = new ReplaySubject() + const [senderReplay, recipientReplay] = setupEventReplaySubjects( + [senderAgent, recipientAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) // Make connection between mediator and recipient const [mediatorRecipientConnection, recipientMediatorConnection] = await makeConnection(mediatorAgent, recipientAgent) From 4f520c28880b1a5521e61b9a141d3faba6e62a54 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 17 Feb 2023 19:13:34 +0100 Subject: [PATCH 15/22] temp Signed-off-by: Timo Glastra --- .../formats/LegacyIndyProofFormatService.ts | 16 +- .../credentials/v1/V1CredentialProtocol.ts | 15 +- .../V1CredentialProtocolCred.test.ts | 39 +-- .../V1CredentialProtocolProposeOffer.test.ts | 24 +- .../v1-connectionless-credentials.e2e.test.ts | 15 +- .../v1-credentials-auto-accept.e2e.test.ts | 32 +- .../v1/__tests__/V1ProofProtocol.test.ts | 16 +- .../v1-connectionless-proofs.e2e.test.ts | 13 +- ... => v1-indy-proof-negotiation.e2e.test.ts} | 44 +-- ...=> v1-indy-proof-presentation.e2e.test.ts} | 44 ++- ....ts => v1-indy-proof-proposal.e2e.test.ts} | 5 +- .../v1-indy-proof-request.e2e.test.ts | 21 +- .../v1/__tests__/v1-indy-proofs.e2e.test.ts | 45 +-- .../__tests__/hasDuplicateGroupNames.test.ts | 10 +- .../src/utils/hasDuplicateGroupNames.ts | 12 +- packages/anoncreds/src/utils/index.ts | 2 +- .../anoncreds/tests/legacyAnonCredsSetup.ts | 29 +- packages/core/src/agent/Agent.ts | 6 +- packages/core/src/agent/MessageSender.ts | 2 +- .../core/src/agent/__tests__/Agent.test.ts | 11 +- .../__tests__/basic-messages.e2e.test.ts | 25 +- .../__tests__/connection-manual.e2e.test.ts | 64 ++-- .../modules/credentials/CredentialsModule.ts | 7 +- .../__tests__/CredentialsModule.test.ts | 2 +- .../protocol/v2/V2CredentialProtocol.ts | 2 +- .../V2CredentialProtocolCred.test.ts | 31 +- .../V2CredentialProtocolOffer.test.ts | 27 +- .../v2-connectionless-credentials.e2e.test.ts | 10 +- .../v2-credentials-auto-accept.e2e.test.ts | 273 ++++++++---------- ...f.credentials.propose-offerED25519.test.ts | 35 ++- .../dids/__tests__/dids-registrar.e2e.test.ts | 12 +- .../dids/__tests__/dids-resolver.e2e.test.ts | 17 +- .../dids/services/DidResolverService.ts | 5 +- .../__tests__/DidResolverService.test.ts | 1 + .../v1-discover-features.e2e.test.ts | 66 +++-- .../v2-discover-features.e2e.test.ts | 61 ++-- .../oob/__tests__/connect-to-self.e2e.test.ts | 14 +- .../v2-indy-proof-negotiation.test.ts | 37 +-- .../routing/__tests__/mediation.test.ts | 35 ++- .../modules/routing/__tests__/pickup.test.ts | 24 +- .../storage/migration/__tests__/0.1.test.ts | 15 + .../storage/migration/__tests__/0.2.test.ts | 12 + .../storage/migration/__tests__/0.3.test.ts | 6 + .../__tests__/UpdateAssistant.test.ts | 8 +- .../migration/__tests__/backup.test.ts | 3 +- .../migration/updates/0.1-0.2/credential.ts | 2 +- packages/core/tests/agents.test.ts | 47 ++- packages/core/tests/connections.test.ts | 80 +++-- packages/core/tests/generic-records.test.ts | 11 +- packages/core/tests/helpers.ts | 37 ++- packages/core/tests/jsonld.ts | 28 +- packages/core/tests/migration.test.ts | 3 +- .../core/tests/multi-protocol-version.test.ts | 54 ++-- .../tests/oob-mediation-provision.test.ts | 70 +++-- packages/core/tests/oob-mediation.test.ts | 41 ++- packages/core/tests/postgres.e2e.test.ts | 21 +- packages/core/tests/wallet.test.ts | 7 +- packages/indy-sdk/src/IndySdkModule.ts | 16 +- packages/indy-sdk/tests/setupIndySdkModule.ts | 6 +- .../tenants/src/__tests__/TenantsApi.test.ts | 5 +- 60 files changed, 839 insertions(+), 782 deletions(-) rename packages/anoncreds/src/protocols/proofs/v1/__tests__/{v1-indy-proof-negotiation.test.ts => v1-indy-proof-negotiation.e2e.test.ts} (91%) rename packages/anoncreds/src/protocols/proofs/v1/__tests__/{v1-indy-proof-presentation.test.e2e.ts => v1-indy-proof-presentation.e2e.test.ts} (88%) rename packages/anoncreds/src/protocols/proofs/v1/__tests__/{v1-indy-proof-proposal.test.e2e.ts => v1-indy-proof-proposal.e2e.test.ts} (97%) diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts index 4c715b9005..c2e5e2d1d5 100644 --- a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -56,12 +56,12 @@ import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistry import { sortRequestedCredentialsMatches, createRequestFromPreview, - hasDuplicateGroupsNamesInProofRequest, areAnonCredsProofRequestsEqual, assertRevocationInterval, downloadTailsFile, checkValidCredentialValueEncoding, encodeCredentialValue, + assertNoDuplicateGroupsNamesInProofRequest, } from '../utils' const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' @@ -104,9 +104,7 @@ export class LegacyIndyProofFormatService implements ProofFormatService ): Promise { @@ -791,7 +780,7 @@ export class V1CredentialProtocol } /** - * Create a {@link IssueCredentialMessage} as response to a received credential request. + * Create a {@link V1IssueCredentialMessage} as response to a received credential request. * * @returns Object containing issue credential message and associated credential record * @@ -860,7 +849,7 @@ export class V1CredentialProtocol } /** - * Process an incoming {@link IssueCredentialMessage} + * Process an incoming {@link V1IssueCredentialMessage} * * @param messageContext The message context containing a credential acknowledgement message * @returns credential record associated with the credential acknowledgement message diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts index 809a8c7f18..eb255070cc 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts @@ -8,9 +8,6 @@ import type { import { EventEmitter, - CredentialRepository, - DidCommMessageRepository, - ConnectionService, DidExchangeState, Attachment, AttachmentData, @@ -20,7 +17,6 @@ import { AriesFrameworkError, CredentialState, CredentialExchangeRecord, - CredentialMetadataKeys, CredentialFormatSpec, AutoAcceptCredential, JsonTransformer, @@ -31,6 +27,9 @@ import { } from '@aries-framework/core' import { Subject } from 'rxjs' +import { ConnectionService } from '../../../../../../core/src/modules/connections/services/ConnectionService' +import { CredentialRepository } from '../../../../../../core/src/modules/credentials/repository/CredentialRepository' +import { DidCommMessageRepository } from '../../../../../../core/src/storage/didcomm/DidCommMessageRepository' import { getMockConnection, getAgentConfig, getAgentContext, mockFunction } from '../../../../../../core/tests/helpers' import { LegacyIndyCredentialFormatService } from '../../../../formats/LegacyIndyCredentialFormatService' import { convertAttributesToCredentialValues } from '../../../../utils/credential' @@ -49,10 +48,10 @@ import { } from '../messages' // Mock classes -jest.mock('../../../repository/CredentialRepository') -jest.mock('../../../formats/indy/IndyCredentialFormatService') -jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') -jest.mock('../../../../connections/services/ConnectionService') +jest.mock('../../../../../../core/src/modules/credentials/repository/CredentialRepository') +jest.mock('../../../../formats/LegacyIndyCredentialFormatService') +jest.mock('../../../../../../core/src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../../../../../core/src/modules/connections/services/ConnectionService') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock @@ -153,25 +152,19 @@ const getAgentMessageMock = async (agentContext: AgentContext, options: { messag // object to test our service would behave correctly. We use type assertion for `offer` attribute to `any`. const mockCredentialRecord = ({ state, - metadata, threadId, connectionId, tags, id, credentialAttributes, - indyRevocationRegistryId, - indyCredentialRevocationId, }: { state?: CredentialState - metadata?: { indyRequest: Record } tags?: CustomCredentialTags threadId?: string connectionId?: string credentialId?: string id?: string credentialAttributes?: CredentialPreviewAttribute[] - indyRevocationRegistryId?: string - indyCredentialRevocationId?: string } = {}) => { const credentialRecord = new CredentialExchangeRecord({ id, @@ -181,7 +174,7 @@ const mockCredentialRecord = ({ connectionId: connectionId ?? '123', credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: '123456', }, ], @@ -189,17 +182,6 @@ const mockCredentialRecord = ({ protocolVersion: 'v1', }) - if (metadata?.indyRequest) { - credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, { ...metadata.indyRequest }) - } - - if (indyCredentialRevocationId || indyRevocationRegistryId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - indyCredentialRevocationId, - indyRevocationRegistryId, - }) - } - return credentialRecord } @@ -286,11 +268,6 @@ describe('V1CredentialProtocol', () => { credentialRecord, attachmentId: INDY_CREDENTIAL_REQUEST_ATTACHMENT_ID, offerAttachment, - credentialFormats: { - indy: { - holderDid: 'did:sov:123456789abcdefghi', - }, - }, }) expect(didCommMessageRepository.saveOrUpdateAgentMessage).toHaveBeenCalledWith(agentContext, { agentMessage: message, diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts index 09e69ee5b7..f4a72ce08c 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts @@ -1,11 +1,6 @@ import type { CredentialProtocolOptions, CredentialStateChangedEvent } from '@aries-framework/core' import { - CredentialRepository, - DidCommMessageRepository, - RoutingService, - ConnectionService, - Dispatcher, EventEmitter, DidExchangeState, Attachment, @@ -19,32 +14,29 @@ import { } from '@aries-framework/core' import { Subject } from 'rxjs' +import { ConnectionService } from '../../../../../../core/src/modules/connections/services/ConnectionService' +import { CredentialRepository } from '../../../../../../core/src/modules/credentials/repository/CredentialRepository' +import { DidCommMessageRepository } from '../../../../../../core/src/storage/didcomm/DidCommMessageRepository' import { getAgentConfig, getAgentContext, getMockConnection, mockFunction } from '../../../../../../core/tests/helpers' import { LegacyIndyCredentialFormatService } from '../../../../formats/LegacyIndyCredentialFormatService' import { V1CredentialProtocol } from '../V1CredentialProtocol' import { V1CredentialPreview, INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' // Mock classes -jest.mock('../../../repository/CredentialRepository') +jest.mock('../../../../../../core/src/modules/credentials/repository/CredentialRepository') jest.mock('../../../../formats/LegacyIndyCredentialFormatService') -jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') -jest.mock('../../../../routing/services/RoutingService') -jest.mock('../../../../connections/services/ConnectionService') -jest.mock('../../../../../agent/Dispatcher') +jest.mock('../../../../../../core/src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../../../../../core/src/modules/connections/services/ConnectionService') // Mock typed object const CredentialRepositoryMock = CredentialRepository as jest.Mock const DidCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock -const RoutingServiceMock = RoutingService as jest.Mock const ConnectionServiceMock = ConnectionService as jest.Mock -const DispatcherMock = Dispatcher as jest.Mock const LegacyIndyCredentialFormatServiceMock = LegacyIndyCredentialFormatService as jest.Mock const credentialRepository = new CredentialRepositoryMock() const didCommMessageRepository = new DidCommMessageRepositoryMock() -const routingService = new RoutingServiceMock() -const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() const indyCredentialFormatService = new LegacyIndyCredentialFormatServiceMock() @@ -55,8 +47,6 @@ const agentContext = getAgentContext({ registerInstances: [ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], - [RoutingService, routingService], - [Dispatcher, dispatcher], [ConnectionService, connectionService], [EventEmitter, eventEmitter], ], @@ -65,7 +55,7 @@ const agentContext = getAgentContext({ // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore -indyCredentialFormatService.credentialRecordType = 'indy' +indyCredentialFormatService.credentialRecordType = 'anoncreds' const connectionRecord = getMockConnection({ id: '123', diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts index 73f07a0b63..975db00a6e 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-connectionless-credentials.e2e.test.ts @@ -19,6 +19,7 @@ describe('V1 Connectionless Credentials', () => { let faberReplay: EventReplaySubject let aliceReplay: EventReplaySubject let credentialDefinitionId: string + let schemaId: string beforeEach(async () => { ;({ @@ -27,6 +28,7 @@ describe('V1 Connectionless Credentials', () => { holderAgent: aliceAgent, holderReplay: aliceReplay, credentialDefinitionId, + schemaId, } = await setupAnonCredsTests({ issuerName: 'Faber connection-less Credentials V1', holderName: 'Alice connection-less Credentials V1', @@ -112,14 +114,15 @@ describe('V1 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { + schemaId, credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -133,7 +136,8 @@ describe('V1 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { + schemaId, credentialDefinitionId, }, }, @@ -193,14 +197,15 @@ describe('V1 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { + schemaId, credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], diff --git a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts index c19b1c1065..6c0455755c 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/v1-credentials-auto-accept.e2e.test.ts @@ -38,12 +38,13 @@ describe('V1 Credentials Auto Accept', () => { holderAgent: aliceAgent, holderReplay: aliceReplay, credentialDefinitionId, + schemaId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, } = await setupAnonCredsTests({ - issuerName: 'Faber connection-less Credentials V1', - holderName: 'Alice connection-less Credentials V1', - attributeNames: ['name', 'age'], + issuerName: 'Faber Credentials Auto Accept V1', + holderName: 'Alice Credentials Auto Accept V1', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], autoAcceptCredentials: AutoAcceptCredential.Always, })) }) @@ -88,7 +89,7 @@ describe('V1 Credentials Auto Accept', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { schemaId: schemaId, credentialDefinitionId: credentialDefinitionId, }, @@ -127,8 +128,8 @@ describe('V1 Credentials Auto Accept', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { schemaId, credentialDefinitionId, }, @@ -136,7 +137,7 @@ describe('V1 Credentials Auto Accept', () => { }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -159,12 +160,13 @@ describe('V1 Credentials Auto Accept', () => { holderAgent: aliceAgent, holderReplay: aliceReplay, credentialDefinitionId, + schemaId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, } = await setupAnonCredsTests({ issuerName: 'faber agent: contentApproved v1', holderName: 'alice agent: contentApproved v1', - attributeNames: ['name', 'age'], + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], autoAcceptCredentials: AutoAcceptCredential.ContentApproved, })) }) @@ -228,8 +230,8 @@ describe('V1 Credentials Auto Accept', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { schemaId, credentialDefinitionId: credentialDefinitionId, }, @@ -237,7 +239,7 @@ describe('V1 Credentials Auto Accept', () => { }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -250,7 +252,7 @@ describe('V1 Credentials Auto Accept', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { schemaId, credentialDefinitionId: credentialDefinitionId, }, @@ -316,8 +318,8 @@ describe('V1 Credentials Auto Accept', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { schemaId, credentialDefinitionId: credentialDefinitionId, }, @@ -325,7 +327,7 @@ describe('V1 Credentials Auto Accept', () => { }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts index 31aa902a05..d3c6a4f204 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts @@ -3,9 +3,6 @@ import type { CustomProofTags, AgentConfig, AgentContext, ProofStateChangedEvent import { Subject } from 'rxjs' import { - ProofRepository, - ConnectionService, - DidCommMessageRepository, DidExchangeState, Attachment, AttachmentData, @@ -16,17 +13,20 @@ import { PresentationProblemReportReason, EventEmitter, } from '../../../../../../core/src' +import { ConnectionService } from '../../../../../../core/src/modules/connections/services/ConnectionService' +import { ProofRepository } from '../../../../../../core/src/modules/proofs/repository/ProofRepository' +import { DidCommMessageRepository } from '../../../../../../core/src/storage/didcomm/DidCommMessageRepository' import { getMockConnection, getAgentConfig, getAgentContext, mockFunction } from '../../../../../../core/tests' -import { LegacyIndyProofFormatService } from '../../../../formats' +import { LegacyIndyProofFormatService } from '../../../../formats/LegacyIndyProofFormatService' import { V1ProofProtocol } from '../V1ProofProtocol' import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '../messages' import { V1PresentationProblemReportMessage } from '../messages/V1PresentationProblemReportMessage' // Mock classes -jest.mock('../../../repository/ProofRepository') -jest.mock('../../../../../storage/didcomm/DidCommMessageRepository') -jest.mock('../../../../connections/services/ConnectionService') -jest.mock('../../../../../storage/Repository') +jest.mock('../../../../../../core/src/modules/proofs/repository/ProofRepository') +jest.mock('../../../../formats/LegacyIndyProofFormatService') +jest.mock('../../../../../../core/src/storage/didcomm/DidCommMessageRepository') +jest.mock('../../../../../../core/src/modules/connections/services/ConnectionService') // Mock typed object const ProofRepositoryMock = ProofRepository as jest.Mock diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts index 71e3d3a09d..a57cc84cb0 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -24,6 +24,7 @@ import { makeConnection, setupEventReplaySubjects, } from '../../../../../../core/tests' +import { getIndySdkModules } from '../../../../../../indy-sdk/tests/setupIndySdkModule' import { getLegacyAnonCredsModules, issueLegacyAnonCredsCredential, @@ -253,10 +254,14 @@ describe('V1 Proofs - Connectionless - Indy', () => { const unique = uuid().substring(0, 4) - const mediatorAgentOptions = getAgentOptions(`Connectionless proofs with mediator Mediator-${unique}`, { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], - }) + const mediatorAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Mediator-${unique}`, + { + autoAcceptMediationRequests: true, + endpoints: ['rxjs:mediator'], + }, + getIndySdkModules() + ) const mediatorMessages = new Subject() const subjectMap = { 'rxjs:mediator': mediatorMessages } diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.e2e.test.ts similarity index 91% rename from packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.e2e.test.ts index f0d467ed7e..917f5c805d 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-negotiation.e2e.test.ts @@ -72,18 +72,13 @@ describe('Present Proof', () => { comment: 'V1 propose proof test 1', presentationProposal: { type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'image_0', - credentialDefinitionId, - }, - ], + attributes: [], predicates: [ { name: 'age', credentialDefinitionId, predicate: '>=', - threshold: 50, + threshold: 18, }, ], }, @@ -196,20 +191,13 @@ describe('Present Proof', () => { comment: 'V1 propose proof test 2', presentationProposal: { type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'name', - credentialDefinitionId, - value: 'John', - referent: '0', - }, - ], + attributes: [], predicates: [ { name: 'age', credentialDefinitionId, predicate: '>=', - threshold: 50, + threshold: 18, }, ], }, @@ -268,20 +256,13 @@ describe('Present Proof', () => { comment: 'V1 propose proof test 2', presentationProposal: { type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'name', - credentialDefinitionId, - value: 'John', - referent: '0', - }, - ], + attributes: [], predicates: [ { name: 'age', credentialDefinitionId, predicate: '>=', - threshold: 50, + threshold: 18, }, ], }, @@ -295,20 +276,11 @@ describe('Present Proof', () => { expect(proofRequestMessage.indyProofRequest).toMatchObject({ name: 'Proof Request', version: '1.0', - requested_attributes: { - '0': { - name: 'name', - restrictions: [ - { - cred_def_id: credentialDefinitionId, - }, - ], - }, - }, + requested_attributes: {}, requested_predicates: { [predicateKey]: { p_type: '>=', - p_value: 50, + p_value: 18, restrictions: [ { cred_def_id: credentialDefinitionId, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.e2e.test.ts similarity index 88% rename from packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.e2e.test.ts index 67ba394844..5b4c358a2f 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.test.e2e.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-presentation.e2e.test.ts @@ -1,27 +1,55 @@ +import type { EventReplaySubject } from '../../../../../../core/tests' import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' import { ProofState, ProofExchangeRecord } from '../../../../../../core/src' import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' -import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' +import { issueLegacyAnonCredsCredential, setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' describe('Present Proof', () => { let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject let aliceConnectionId: string + let faberConnectionId: string let credentialDefinitionId: string beforeAll(async () => { testLogger.test('Initializing the agents') ;({ issuerAgent: faberAgent, + issuerReplay: faberReplay, holderAgent: aliceAgent, + holderReplay: aliceReplay, credentialDefinitionId, holderIssuerConnectionId: aliceConnectionId, + issuerHolderConnectionId: faberConnectionId, } = await setupAnonCredsTests({ issuerName: 'Faber - V1 Indy Proof', holderName: 'Alice - V1 Indy Proof', attributeNames: ['name', 'age'], })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '55', + }, + ], + }, + }) }) afterAll(async () => { @@ -51,6 +79,7 @@ describe('Present Proof', () => { name: 'name', value: 'John', credentialDefinitionId, + referent: '0', }, ], predicates: [ @@ -84,10 +113,6 @@ describe('Present Proof', () => { value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId, - }, ], predicates: [ { @@ -175,15 +200,6 @@ describe('Present Proof', () => { }, }, ], - appendedAttachments: [ - { - id: expect.any(String), - filename: expect.any(String), - data: { - base64: expect.any(String), - }, - }, - ], thread: { threadId: expect.any(String), }, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.e2e.test.ts similarity index 97% rename from packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts rename to packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.e2e.test.ts index 8836e7a750..14e9e72145 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.test.e2e.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.e2e.test.ts @@ -51,6 +51,7 @@ describe('Present Proof', () => { name: 'name', value: 'John', credentialDefinitionId, + referent: '0', }, ], predicates: [ @@ -83,10 +84,6 @@ describe('Present Proof', () => { value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId, - }, ], predicates: [ { diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts index 3d20b3af5b..36e9203b0d 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-request.e2e.test.ts @@ -32,7 +32,7 @@ describe('Present Proof | V1ProofProtocol', () => { await aliceAgent.wallet.delete() }) - test(`Alice Creates and sends Proof Proposal to Faber`, async () => { + test(`Alice Creates and sends Proof Proposal to Faber and Faber accepts the proposal`, async () => { testLogger.test('Alice sends proof proposal to Faber') const faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { @@ -51,6 +51,7 @@ describe('Present Proof | V1ProofProtocol', () => { name: 'name', value: 'John', credentialDefinitionId, + referent: '0', }, ], predicates: [ @@ -70,28 +71,24 @@ describe('Present Proof | V1ProofProtocol', () => { let faberProofExchangeRecord = await faberProofExchangeRecordPromise const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) - expect(proposal).toMatchObject({ - type: 'https://didcomm.org/present-proof/1.0/propose-presentation', - id: expect.any(String), + expect(proposal?.toJSON()).toMatchObject({ + '@type': 'https://didcomm.org/present-proof/1.0/propose-presentation', + '@id': expect.any(String), comment: 'V1 propose proof test', - presentationProposal: { - type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + presentation_proposal: { + '@type': 'https://didcomm.org/present-proof/1.0/presentation-preview', attributes: [ { name: 'name', - credentialDefinitionId, + cred_def_id: credentialDefinitionId, value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId, - }, ], predicates: [ { name: 'age', - credentialDefinitionId, + cred_def_id: credentialDefinitionId, predicate: '>=', threshold: 50, }, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts index 9a59182b41..ff71996463 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proofs.e2e.test.ts @@ -26,8 +26,8 @@ describe('Present Proof', () => { issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, } = await setupAnonCredsTests({ - issuerName: 'Faber Auto Accept Always Proofs', - holderName: 'Alice Auto Accept Always Proofs', + issuerName: 'Faber Proofs V1 - Full', + holderName: 'Alice Proofs V1 - Full', attributeNames: ['name', 'age'], })) @@ -73,6 +73,7 @@ describe('Present Proof', () => { name: 'name', value: 'John', credentialDefinitionId, + referent: '0', }, ], predicates: [ @@ -103,10 +104,6 @@ describe('Present Proof', () => { value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId, - }, ], predicates: [ { @@ -246,6 +243,9 @@ describe('Present Proof', () => { const formatData = await aliceAgent.proofs.getFormatData(aliceProofExchangeRecord.id) + const proposalPredicateKey = Object.keys(formatData.proposal?.indy?.requested_predicates || {})[0] + const requestPredicateKey = Object.keys(formatData.request?.indy?.requested_predicates || {})[0] + expect(formatData).toMatchObject({ proposal: { indy: { @@ -256,17 +256,9 @@ describe('Present Proof', () => { 0: { name: 'name', }, - something: { - name: 'image_0', - restrictions: [ - { - cred_def_id: credentialDefinitionId, - }, - ], - }, }, requested_predicates: { - something_else: { + [proposalPredicateKey]: { name: 'age', p_type: '>=', p_value: 50, @@ -288,17 +280,9 @@ describe('Present Proof', () => { 0: { name: 'name', }, - something: { - name: 'image_0', - restrictions: [ - { - cred_def_id: credentialDefinitionId, - }, - ], - }, }, requested_predicates: { - something_else: { + [requestPredicateKey]: { name: 'age', p_type: '>=', p_value: 50, @@ -421,15 +405,6 @@ describe('Present Proof', () => { }, }, ], - // appendedAttachments: [ - // { - // id: expect.any(String), - // filename: expect.any(String), - // data: { - // base64: expect.any(String), - // }, - // }, - // ], thread: { threadId: expect.any(String), }, @@ -485,8 +460,8 @@ describe('Present Proof', () => { name: 'proof-request', version: '1.0', requested_attributes: { - name: { - name: 'name', + age: { + name: 'age', restrictions: [ { cred_def_id: credentialDefinitionId, diff --git a/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts b/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts index 4e7bab2ddd..c4deb02be7 100644 --- a/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts +++ b/packages/anoncreds/src/utils/__tests__/hasDuplicateGroupNames.test.ts @@ -1,10 +1,10 @@ import type { AnonCredsProofRequest } from '../../models' -import { hasDuplicateGroupsNamesInProofRequest } from '../hasDuplicateGroupNames' +import { assertNoDuplicateGroupsNamesInProofRequest } from '../hasDuplicateGroupNames' const credentialDefinitionId = '9vPXgSpQJPkJEALbLXueBp:3:CL:57753:tag1' -describe('util | hasDuplicateGroupsNamesInProofRequest', () => { +describe('util | assertNoDuplicateGroupsNamesInProofRequest', () => { describe('assertNoDuplicateGroupsNamesInProofRequest', () => { test('attribute names match', () => { const proofRequest = { @@ -32,7 +32,7 @@ describe('util | hasDuplicateGroupsNamesInProofRequest', () => { requested_predicates: {}, } satisfies AnonCredsProofRequest - expect(hasDuplicateGroupsNamesInProofRequest(proofRequest)).toBe(false) + expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).not.toThrow() }) test('attribute names match with predicates name', () => { @@ -64,7 +64,9 @@ describe('util | hasDuplicateGroupsNamesInProofRequest', () => { }, } satisfies AnonCredsProofRequest - expect(hasDuplicateGroupsNamesInProofRequest(proofRequest)).toBe(true) + expect(() => assertNoDuplicateGroupsNamesInProofRequest(proofRequest)).toThrowError( + 'The proof request contains duplicate predicates and attributes: age' + ) }) }) }) diff --git a/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts b/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts index f4915fe6fc..7a16743eb9 100644 --- a/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts +++ b/packages/anoncreds/src/utils/hasDuplicateGroupNames.ts @@ -1,5 +1,7 @@ import type { AnonCredsProofRequest } from '../models' +import { AriesFrameworkError } from '@aries-framework/core' + function attributeNamesToArray(proofRequest: AnonCredsProofRequest) { // Attributes can contain either a `name` string value or an `names` string array. We reduce it to a single array // containing all attribute names from the requested attributes. @@ -14,10 +16,14 @@ function predicateNamesToArray(proofRequest: AnonCredsProofRequest) { } // TODO: This is still not ideal. The requested groups can specify different credentials using restrictions. -export function hasDuplicateGroupsNamesInProofRequest(proofRequest: AnonCredsProofRequest) { +export function assertNoDuplicateGroupsNamesInProofRequest(proofRequest: AnonCredsProofRequest) { const attributes = attributeNamesToArray(proofRequest) const predicates = predicateNamesToArray(proofRequest) - const duplicates = predicates.find((item) => attributes.indexOf(item) !== -1) - return duplicates !== undefined + const duplicates = predicates.filter((item) => attributes.indexOf(item) !== -1) + if (duplicates.length > 0) { + throw new AriesFrameworkError( + `The proof request contains duplicate predicates and attributes: ${duplicates.toString()}` + ) + } } diff --git a/packages/anoncreds/src/utils/index.ts b/packages/anoncreds/src/utils/index.ts index 5e1ff3f413..2de326adf2 100644 --- a/packages/anoncreds/src/utils/index.ts +++ b/packages/anoncreds/src/utils/index.ts @@ -1,6 +1,6 @@ export { createRequestFromPreview } from './createRequestFromPreview' export { sortRequestedCredentialsMatches } from './sortRequestedCredentialsMatches' -export { hasDuplicateGroupsNamesInProofRequest } from './hasDuplicateGroupNames' +export { assertNoDuplicateGroupsNamesInProofRequest } from './hasDuplicateGroupNames' export { areAnonCredsProofRequestsEqual } from './areRequestsEqual' export { downloadTailsFile } from './tails' export { assertRevocationInterval } from './revocationInterval' diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 32c3471c39..687447e6b6 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -268,6 +268,7 @@ interface SetupAnonCredsTestsReturn extends BaseAge public async initialize() { await super.initialize() - for (const [moduleKey, module] of Object.entries(this.dependencyManager.registeredModules) as [string, Module][]) { - this.logger.debug(`initializing module ${moduleKey}`, { - hasInitializationMethod: module.initialize !== undefined, - }) - + for (const [, module] of Object.entries(this.dependencyManager.registeredModules) as [string, Module][]) { if (module.initialize) { await module.initialize(this.agentContext) } diff --git a/packages/core/src/agent/MessageSender.ts b/packages/core/src/agent/MessageSender.ts index 04aad34d38..796da85243 100644 --- a/packages/core/src/agent/MessageSender.ts +++ b/packages/core/src/agent/MessageSender.ts @@ -235,7 +235,7 @@ export class MessageSender { outOfBand )) } catch (error) { - this.logger.error(`Unable to retrieve services for connection '${connection.id}`) + this.logger.error(`Unable to retrieve services for connection '${connection.id}. ${error.message}`) this.emitMessageSentEvent(outboundMessageContext, OutboundMessageSendStatus.Undeliverable) throw new MessageSendingError(`Unable to retrieve services for connection '${connection.id}`, { outboundMessageContext, diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index f9760a5ff0..6402a898b0 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -2,6 +2,7 @@ import type { DependencyManager, Module } from '../../plugins' import { injectable } from 'tsyringe' +import { getIndySdkModules } from '../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions } from '../../../tests/helpers' import { InjectionSymbols } from '../../constants' import { BasicMessageRepository, BasicMessageService } from '../../modules/basic-messages' @@ -31,7 +32,7 @@ import { FeatureRegistry } from '../FeatureRegistry' import { MessageReceiver } from '../MessageReceiver' import { MessageSender } from '../MessageSender' -const agentOptions = getAgentOptions('Agent Class Test') +const agentOptions = getAgentOptions('Agent Class Test', {}, getIndySdkModules()) const myModuleMethod = jest.fn() @injectable() @@ -59,6 +60,7 @@ describe('Agent', () => { ...agentOptions, modules: { myModule: new MyModule(), + ...getIndySdkModules(), }, }) @@ -76,6 +78,7 @@ describe('Agent', () => { mediationRecipient: new RecipientModule({ maximumMessagePickup: 42, }), + ...getIndySdkModules(), }, }) @@ -241,17 +244,13 @@ describe('Agent', () => { 'https://didcomm.org/didexchange/1.0', 'https://didcomm.org/discover-features/1.0', 'https://didcomm.org/discover-features/2.0', - 'https://didcomm.org/issue-credential/1.0', - 'https://didcomm.org/issue-credential/2.0', 'https://didcomm.org/messagepickup/1.0', 'https://didcomm.org/messagepickup/2.0', 'https://didcomm.org/out-of-band/1.1', - 'https://didcomm.org/present-proof/1.0', - 'https://didcomm.org/present-proof/2.0', 'https://didcomm.org/revocation_notification/1.0', 'https://didcomm.org/revocation_notification/2.0', ]) ) - expect(protocols.length).toEqual(15) + expect(protocols.length).toEqual(11) }) }) diff --git a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts index e4e2d0dd17..21d80883e1 100644 --- a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts +++ b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts @@ -6,6 +6,7 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions, makeConnection, waitForBasicMessage } from '../../../../tests/helpers' import testLogger from '../../../../tests/logger' import { Agent } from '../../../agent/Agent' @@ -13,13 +14,23 @@ import { MessageSendingError, RecordNotFoundError } from '../../../error' import { BasicMessage } from '../messages' import { BasicMessageRecord } from '../repository' -const faberConfig = getAgentOptions('Faber Basic Messages', { - endpoints: ['rxjs:faber'], -}) - -const aliceConfig = getAgentOptions('Alice Basic Messages', { - endpoints: ['rxjs:alice'], -}) +const modules = getIndySdkModules() + +const faberConfig = getAgentOptions( + 'Faber Basic Messages', + { + endpoints: ['rxjs:faber'], + }, + modules +) + +const aliceConfig = getAgentOptions( + 'Alice Basic Messages', + { + endpoints: ['rxjs:alice'], + }, + modules +) describe('Basic Messages E2E', () => { let faberAgent: Agent diff --git a/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts index dc07e9639f..b6dfe6a813 100644 --- a/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts +++ b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts @@ -4,8 +4,8 @@ import type { ConnectionStateChangedEvent } from '../ConnectionEvents' import { firstValueFrom } from 'rxjs' import { filter, first, map, timeout } from 'rxjs/operators' -import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' +import { setupSubjectTransports } from '../../../../tests' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { ConnectionEventTypes } from '../ConnectionEvents' @@ -41,46 +41,44 @@ function waitForResponse(agent: Agent, connectionId: string) { ) } +const modules = getIndySdkModules() + describe('Manual Connection Flow', () => { // This test was added to reproduce a bug where all connections based on a reusable invitation would use the same keys // This was only present in the manual flow, which is almost never used. it('can connect multiple times using the same reusable invitation without manually using the connections api', async () => { - const aliceInboundTransport = new SubjectInboundTransport() - const bobInboundTransport = new SubjectInboundTransport() - const faberInboundTransport = new SubjectInboundTransport() - - const subjectMap = { - 'rxjs:faber': faberInboundTransport.ourSubject, - 'rxjs:alice': aliceInboundTransport.ourSubject, - 'rxjs:bob': bobInboundTransport.ourSubject, - } - const aliceAgentOptions = getAgentOptions('Manual Connection Flow Alice', { - label: 'alice', - autoAcceptConnections: false, - endpoints: ['rxjs:alice'], - }) - const bobAgentOptions = getAgentOptions('Manual Connection Flow Bob', { - label: 'bob', - autoAcceptConnections: false, - endpoints: ['rxjs:bob'], - }) - const faberAgentOptions = getAgentOptions('Manual Connection Flow Faber', { - autoAcceptConnections: false, - endpoints: ['rxjs:faber'], - }) + const aliceAgentOptions = getAgentOptions( + 'Manual Connection Flow Alice', + { + label: 'alice', + autoAcceptConnections: false, + endpoints: ['rxjs:alice'], + }, + modules + ) + const bobAgentOptions = getAgentOptions( + 'Manual Connection Flow Bob', + { + label: 'bob', + autoAcceptConnections: false, + endpoints: ['rxjs:bob'], + }, + modules + ) + const faberAgentOptions = getAgentOptions( + 'Manual Connection Flow Faber', + { + autoAcceptConnections: false, + endpoints: ['rxjs:faber'], + }, + modules + ) const aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(aliceInboundTransport) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - const bobAgent = new Agent(bobAgentOptions) - bobAgent.registerInboundTransport(bobInboundTransport) - bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - const faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(faberInboundTransport) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + setupSubjectTransports([aliceAgent, bobAgent, faberAgent]) await aliceAgent.initialize() await bobAgent.initialize() await faberAgent.initialize() diff --git a/packages/core/src/modules/credentials/CredentialsModule.ts b/packages/core/src/modules/credentials/CredentialsModule.ts index 1a09ff1b0f..9cae75eb28 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -1,6 +1,5 @@ import type { CredentialsModuleConfigOptions } from './CredentialsModuleConfig' import type { CredentialProtocol } from './protocol/CredentialProtocol' -import type { V2CredentialProtocol } from './protocol/v2' import type { FeatureRegistry } from '../../agent/FeatureRegistry' import type { ApiModule, DependencyManager } from '../../plugins' import type { Constructor } from '../../utils/mixins' @@ -11,12 +10,13 @@ import { Protocol } from '../../agent/models' import { CredentialsApi } from './CredentialsApi' import { CredentialsModuleConfig } from './CredentialsModuleConfig' import { RevocationNotificationService } from './protocol/revocation-notification/services' +import { V2CredentialProtocol } from './protocol/v2' import { CredentialRepository } from './repository' /** * Default credentialProtocols that will be registered if the `credentialProtocols` property is not configured. */ -export type DefaultCredentialProtocols = [V2CredentialProtocol<[]>] +export type DefaultCredentialProtocols = [] // CredentialsModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. export type CredentialsModuleOptions = Optional< @@ -37,8 +37,7 @@ export class CredentialsModule } diff --git a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts index d82af72e21..49a9028262 100644 --- a/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts +++ b/packages/core/src/modules/credentials/__tests__/CredentialsModule.test.ts @@ -51,7 +51,7 @@ describe('CredentialsModule', () => { ) }) - test('registers V1CredentialProtocol and V2CredentialProtocol if no credentialProtocols are configured', () => { + test('registers V2CredentialProtocol if no credentialProtocols are configured', () => { const credentialsModule = new CredentialsModule() expect(credentialsModule.config.credentialProtocols).toEqual([expect.any(V2CredentialProtocol)]) diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts index ee6fdd9ace..901834a106 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts @@ -698,7 +698,7 @@ export class V2CredentialProtocol ) => ({ attachment: credentialAttachment, format: credentialFormat }), - deleteCredentialById: jest.fn() as CredentialFormatService['deleteCredentialById'], -} as TestCredentialFormatService + deleteCredentialById: jest.fn(), + processCredential: jest.fn(), + acceptOffer: () => ({ attachment: requestAttachment, format: requestFormat }), + processRequest: jest.fn(), +} as unknown as TestCredentialFormatService describe('credentialProtocol', () => { let credentialProtocol: V2CredentialProtocol @@ -261,7 +264,7 @@ describe('credentialProtocol', () => { }) describe('acceptOffer', () => { - test(`updates state to ${CredentialState.RequestSent}, set request metadata`, async () => { + test(`updates state to ${CredentialState.RequestSent}`, async () => { const credentialRecord = mockCredentialRecord({ state: CredentialState.OfferReceived, threadId: 'fd9c5ddb-ec11-4acd-bc32-540736249746', @@ -400,11 +403,6 @@ describe('credentialProtocol', () => { describe('acceptRequest', () => { test(`updates state to ${CredentialState.CredentialIssued}`, async () => { - mockFunction(testCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -426,11 +424,6 @@ describe('credentialProtocol', () => { }) test(`emits stateChange event from ${CredentialState.RequestReceived} to ${CredentialState.CredentialIssued}`, async () => { - mockFunction(testCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -464,11 +457,6 @@ describe('credentialProtocol', () => { }) test('returns credential response message base on credential request message', async () => { - mockFunction(testCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -513,10 +501,7 @@ describe('credentialProtocol', () => { // given mockFunction(credentialRepository.getSingleByQuery).mockResolvedValue(credentialRecord) - // when - const record = await credentialProtocol.processCredential(messageContext) - - expect(record.credentialAttributes?.length).toBe(2) + await credentialProtocol.processCredential(messageContext) }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts index f1c3b5fb9c..d8d6a42dcb 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts @@ -1,6 +1,11 @@ import type { AgentContext } from '../../../../../agent' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CredentialFormat, CredentialFormatCreateOfferOptions, CredentialFormatService } from '../../../formats' +import type { + CredentialFormat, + CredentialFormatAcceptRequestOptions, + CredentialFormatCreateOfferOptions, + CredentialFormatService, +} from '../../../formats' import type { CreateCredentialOfferOptions } from '../../CredentialProtocolOptions' import { Subject } from 'rxjs' @@ -55,8 +60,26 @@ export const testCredentialFormatService = { ) => ({ attachment: offerAttachment, format: offerFormat, + previewAttributes: [ + { + mimeType: 'text/plain', + name: 'name', + value: 'John', + }, + { + mimeType: 'text/plain', + name: 'age', + value: '99', + }, + ], }), -} as TestCredentialFormatService + acceptRequest: jest.fn(), + deleteCredentialById: jest.fn(), + processCredential: jest.fn(), + acceptOffer: jest.fn(), + processRequest: jest.fn(), + processOffer: jest.fn(), +} as unknown as TestCredentialFormatService // Mock classes jest.mock('../../../repository/CredentialRepository') diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index 8116f08d9f..95c48a3ee9 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -160,14 +160,14 @@ describe('V2 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -181,7 +181,7 @@ describe('V2 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { credentialDefinitionId, }, }, @@ -241,14 +241,14 @@ describe('V2 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts index 6d23c2d6d5..8d1b7cda57 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials-auto-accept.e2e.test.ts @@ -1,11 +1,9 @@ import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' -import type { AcceptCredentialOfferOptions, AcceptCredentialProposalOptions } from '../../../CredentialsApiOptions' -import type { Schema } from 'indy-sdk' +import type { EventReplaySubject } from '../../../../../../tests' import { setupAnonCredsTests } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' -import { waitForCredentialRecord } from '../../../../../../tests/helpers' +import { waitForCredentialRecord, waitForCredentialRecordSubject } from '../../../../../../tests/helpers' import testLogger from '../../../../../../tests/logger' -import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { AutoAcceptCredential } from '../../../models/CredentialAutoAcceptType' import { CredentialState } from '../../../models/CredentialState' import { CredentialExchangeRecord } from '../../../repository/CredentialExchangeRecord' @@ -13,9 +11,11 @@ import { V2CredentialPreview } from '../messages/V2CredentialPreview' describe('V2 Credentials Auto Accept', () => { let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject let credentialDefinitionId: string - let schema: Schema + let schemaId: string let faberConnectionId: string let aliceConnectionId: string @@ -36,8 +36,11 @@ describe('V2 Credentials Auto Accept', () => { beforeAll(async () => { ;({ issuerAgent: faberAgent, + issuerReplay: faberReplay, holderAgent: aliceAgent, + holderReplay: aliceReplay, credentialDefinitionId, + schemaId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, } = await setupAnonCredsTests({ @@ -56,18 +59,8 @@ describe('V2 Credentials Auto Accept', () => { }) test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'always'", async () => { - testLogger.test('Alice begins listening for credential') - const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber begins listening for credential ack') - const faberCredAckPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.Done, - }) - testLogger.test('Alice sends credential proposal to Faber') - await aliceAgent.credentials.proposeCredential({ + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { @@ -80,10 +73,16 @@ describe('V2 Credentials Auto Accept', () => { }) testLogger.test('Alice waits for credential from Faber') - let aliceCredentialRecord = await aliceCredReceivedPromise + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.Done, + threadId: aliceCredentialRecord.threadId, + }) testLogger.test('Faber waits for credential ack from Alice') - aliceCredentialRecord = await faberCredAckPromise + await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.Done, + threadId: aliceCredentialRecord.threadId, + }) expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, @@ -91,8 +90,8 @@ describe('V2 Credentials Auto Accept', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { - schemaId: schema.id, + '_anonCreds/anonCredsCredential': { + schemaId, credentialDefinitionId: credentialDefinitionId, }, }, @@ -102,19 +101,8 @@ describe('V2 Credentials Auto Accept', () => { }) test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'always'", async () => { - testLogger.test('Alice begins listening for credential') - const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.CredentialReceived, - }) - - testLogger.test('Faber begins listening for credential ack') - const faberCredAckPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.Done, - }) - testLogger.test('Faber sends credential offer to Alice') - const schemaId = schema.id - await faberAgent.credentials.offerCredential({ + let faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', connectionId: faberConnectionId, credentialFormats: { @@ -125,16 +113,21 @@ describe('V2 Credentials Auto Accept', () => { }, protocolVersion: 'v2', }) + testLogger.test('Alice waits for credential from Faber') - const aliceCredentialRecord = await aliceCredReceivedPromise + const aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.CredentialReceived, + threadId: faberCredentialRecord.threadId, + }) + expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { schemaId, credentialDefinitionId: credentialDefinitionId, }, @@ -142,7 +135,7 @@ describe('V2 Credentials Auto Accept', () => { }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -150,7 +143,10 @@ describe('V2 Credentials Auto Accept', () => { }) testLogger.test('Faber waits for credential ack from Alice') - const faberCredentialRecord: CredentialExchangeRecord = await faberCredAckPromise + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.Done, + threadId: faberCredentialRecord.threadId, + }) expect(faberCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), @@ -165,14 +161,17 @@ describe('V2 Credentials Auto Accept', () => { beforeAll(async () => { ;({ issuerAgent: faberAgent, + issuerReplay: faberReplay, holderAgent: aliceAgent, + holderReplay: aliceReplay, credentialDefinitionId, + schemaId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, } = await setupAnonCredsTests({ issuerName: 'Faber Agent: Always V2', holderName: 'Alice Agent: Always V2', - autoAcceptCredentials: AutoAcceptCredential.Always, + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], })) }) @@ -186,14 +185,8 @@ describe('V2 Credentials Auto Accept', () => { test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'contentApproved'", async () => { testLogger.test('Alice sends credential proposal to Faber') - const schemaId = schema.id - - testLogger.test('Faber starts listening for credential proposal from Alice') - const faberPropReceivedPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.ProposalReceived, - }) - await aliceAgent.credentials.proposeCredential({ + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { @@ -205,20 +198,14 @@ describe('V2 Credentials Auto Accept', () => { }) testLogger.test('Faber waits for credential proposal from Alice') - const faberPropReceivedRecord = await faberPropReceivedPromise - - const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { - threadId: faberPropReceivedRecord.threadId, - state: CredentialState.CredentialReceived, - }) - - const faberCredAckPromise = waitForCredentialRecord(faberAgent, { - threadId: faberPropReceivedRecord.threadId, - state: CredentialState.Done, + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.ProposalReceived, + threadId: aliceCredentialRecord.threadId, }) - const options: AcceptCredentialProposalOptions = { - credentialRecordId: faberPropReceivedRecord.id, + testLogger.test('Faber sends credential offer to Alice') + await faberAgent.credentials.acceptProposal({ + credentialRecordId: faberCredentialRecord.id, comment: 'V2 Indy Offer', credentialFormats: { indy: { @@ -226,22 +213,27 @@ describe('V2 Credentials Auto Accept', () => { attributes: credentialPreview.attributes, }, }, - } - testLogger.test('Faber sends credential offer to Alice') - options.credentialRecordId = faberPropReceivedRecord.id - await faberAgent.credentials.acceptProposal(options) + }) testLogger.test('Alice waits for credential from Faber') - const aliceCredReceivedRecord = await aliceCredReceivedPromise + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.Done, + threadId: faberCredentialRecord.threadId, + }) - expect(aliceCredReceivedRecord).toMatchObject({ + faberCredentialRecord = await waitForCredentialRecord(faberAgent, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { schemaId, credentialDefinitionId: credentialDefinitionId, }, @@ -249,23 +241,20 @@ describe('V2 Credentials Auto Accept', () => { }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], - state: CredentialState.CredentialReceived, + state: CredentialState.Done, }) - testLogger.test('Faber waits for credential ack from Alice') - const faberCredAckRecord = await faberCredAckPromise - - expect(faberCredAckRecord).toMatchObject({ + expect(faberCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { schemaId, credentialDefinitionId: credentialDefinitionId, }, @@ -276,14 +265,8 @@ describe('V2 Credentials Auto Accept', () => { }) test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'contentApproved'", async () => { - testLogger.test('Alice starts listening for credential offer from Faber') - const aliceOfferReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.OfferReceived, - }) - testLogger.test('Faber sends credential offer to Alice') - const schemaId = schema.id - await faberAgent.credentials.offerCredential({ + let faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', connectionId: faberConnectionId, credentialFormats: { @@ -296,47 +279,40 @@ describe('V2 Credentials Auto Accept', () => { }) testLogger.test('Alice waits for credential offer from Faber') - const aliceOfferReceivedRecord = await aliceOfferReceivedPromise - - expect(JsonTransformer.toJSON(aliceOfferReceivedRecord)).toMatchObject({ + let aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { state: CredentialState.OfferReceived, + threadId: faberCredentialRecord.threadId, }) // below values are not in json object - expect(aliceOfferReceivedRecord.id).not.toBeNull() - expect(aliceOfferReceivedRecord.getTags()).toEqual({ - threadId: aliceOfferReceivedRecord.threadId, - state: aliceOfferReceivedRecord.state, + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, connectionId: aliceConnectionId, credentialIds: [], }) testLogger.test('Alice received credential offer from Faber') - testLogger.test('Alice starts listening for credential from Faber') - const aliceCredReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.CredentialReceived, + testLogger.test('alice sends credential request to faber') + await aliceAgent.credentials.acceptOffer({ + credentialRecordId: aliceCredentialRecord.id, }) - const faberCredAckPromise = waitForCredentialRecord(faberAgent, { + testLogger.test('Alice waits for credential from Faber') + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { state: CredentialState.Done, + threadId: faberCredentialRecord.threadId, }) - const acceptOfferOptions: AcceptCredentialOfferOptions = { - credentialRecordId: aliceOfferReceivedRecord.id, - } - testLogger.test('alice sends credential request to faber') - await aliceAgent.credentials.acceptOffer(acceptOfferOptions) - - testLogger.test('Alice waits for credential from Faber') - const aliceCredReceivedRecord = await aliceCredReceivedPromise - expect(aliceCredReceivedRecord).toMatchObject({ + expect(aliceCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { schemaId, credentialDefinitionId: credentialDefinitionId, }, @@ -344,17 +320,21 @@ describe('V2 Credentials Auto Accept', () => { }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], - state: CredentialState.CredentialReceived, + state: CredentialState.Done, }) testLogger.test('Faber waits for credential ack from Alice') - const faberCredAckRecord = await faberCredAckPromise - expect(faberCredAckRecord).toMatchObject({ + faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + threadId: faberCredentialRecord.threadId, + state: CredentialState.Done, + }) + + expect(faberCredentialRecord).toMatchObject({ type: CredentialExchangeRecord.type, id: expect.any(String), createdAt: expect.any(Date), @@ -363,13 +343,8 @@ describe('V2 Credentials Auto Accept', () => { }) test("Alice starts with V2 credential proposal to Faber, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { - testLogger.test('Faber starts listening for proposal from Alice') - const faberPropReceivedPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.ProposalReceived, - }) - testLogger.test('Alice sends credential proposal to Faber') - const aliceCredProposal = await aliceAgent.credentials.proposeCredential({ + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { @@ -380,19 +355,17 @@ describe('V2 Credentials Auto Accept', () => { }, comment: 'v2 propose credential test', }) - expect(aliceCredProposal.state).toBe(CredentialState.ProposalSent) + expect(aliceCredentialRecord.state).toBe(CredentialState.ProposalSent) testLogger.test('Faber waits for credential proposal from Alice') - const faberPropReceivedRecord = await faberPropReceivedPromise - - testLogger.test('Alice starts listening for credential offer from Faber') - const aliceOfferReceivedPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.OfferReceived, + let faberCredentialRecord = await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.ProposalReceived, + threadId: aliceCredentialRecord.threadId, }) testLogger.test('Faber negotiated proposal, sending credential offer to Alice') - const faberOfferSentRecord = await faberAgent.credentials.negotiateProposal({ - credentialRecordId: faberPropReceivedRecord.id, + faberCredentialRecord = await faberAgent.credentials.negotiateProposal({ + credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { credentialDefinitionId: credentialDefinitionId, @@ -402,33 +375,24 @@ describe('V2 Credentials Auto Accept', () => { }) testLogger.test('Alice waits for credential offer from Faber') - const aliceOfferReceivedRecord = await aliceOfferReceivedPromise + aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.OfferReceived, + threadId: faberCredentialRecord.threadId, + }) // below values are not in json object - expect(aliceOfferReceivedRecord.id).not.toBeNull() - expect(aliceOfferReceivedRecord.getTags()).toEqual({ - threadId: aliceOfferReceivedRecord.threadId, - state: aliceOfferReceivedRecord.state, + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, connectionId: aliceConnectionId, credentialIds: [], }) - - // Check if the state of the credential records did not change - const faberRecord = await faberAgent.credentials.getById(faberOfferSentRecord.id) - faberRecord.assertState(CredentialState.OfferSent) - - const aliceRecord = await aliceAgent.credentials.getById(aliceOfferReceivedRecord.id) - aliceRecord.assertState(CredentialState.OfferReceived) }) test("Faber starts with V2 credential offer to Alice, both have autoAcceptCredential on 'contentApproved' and attributes did change", async () => { - testLogger.test('Alice starts listening for offer from Faber') - const aliceCredentialExchangeRecordPromise = waitForCredentialRecord(aliceAgent, { - state: CredentialState.OfferReceived, - }) - testLogger.test('Faber sends credential offer to Alice') - await faberAgent.credentials.offerCredential({ + const faberCredentialRecord = await faberAgent.credentials.offerCredential({ comment: 'some comment about credential', connectionId: faberConnectionId, credentialFormats: { @@ -441,25 +405,23 @@ describe('V2 Credentials Auto Accept', () => { }) testLogger.test('Alice waits for credential offer from Faber') - const aliceOfferReceivedRecord = await aliceCredentialExchangeRecordPromise + const aliceCredentialRecord = await waitForCredentialRecordSubject(aliceReplay, { + state: CredentialState.OfferReceived, + threadId: faberCredentialRecord.threadId, + }) // below values are not in json object - expect(aliceOfferReceivedRecord.id).not.toBeNull() - expect(aliceOfferReceivedRecord.getTags()).toEqual({ - threadId: aliceOfferReceivedRecord.threadId, - state: aliceOfferReceivedRecord.state, + expect(aliceCredentialRecord.id).not.toBeNull() + expect(aliceCredentialRecord.getTags()).toEqual({ + threadId: aliceCredentialRecord.threadId, + state: aliceCredentialRecord.state, connectionId: aliceConnectionId, credentialIds: [], }) - testLogger.test('Faber starts listening for proposal received') - const faberProposalReceivedPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.ProposalReceived, - }) - testLogger.test('Alice sends credential request to Faber') - const aliceCredRequestRecord = await aliceAgent.credentials.negotiateOffer({ - credentialRecordId: aliceOfferReceivedRecord.id, + await aliceAgent.credentials.negotiateOffer({ + credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { attributes: newCredentialPreview.attributes, @@ -469,15 +431,10 @@ describe('V2 Credentials Auto Accept', () => { comment: 'v2 propose credential test', }) - testLogger.test('Faber waits for credential proposal from Alice') - const faberCredProposalRecord = await faberProposalReceivedPromise - - // Check if the state of fabers credential record did not change - const faberRecord = await faberAgent.credentials.getById(faberCredProposalRecord.id) - faberRecord.assertState(CredentialState.ProposalReceived) - - const aliceRecord = await aliceAgent.credentials.getById(aliceCredRequestRecord.id) - aliceRecord.assertState(CredentialState.ProposalSent) + await waitForCredentialRecordSubject(faberReplay, { + state: CredentialState.ProposalReceived, + threadId: aliceCredentialRecord.threadId, + }) }) }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index b97b0d839c..7e167fb4d0 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -26,12 +26,13 @@ import { getAgentOptions, waitForCredentialRecordSubject, testLogger, + makeConnection, } from '../../../../../../tests' import { Agent } from '../../../../../agent/Agent' import { KeyType } from '../../../../../crypto' import { JsonTransformer } from '../../../../../utils/JsonTransformer' import { CacheModule, InMemoryLruCache } from '../../../../cache' -import { DidsModule } from '../../../../dids' +import { DidsModule, KeyDidRegistrar, KeyDidResolver } from '../../../../dids' import { ProofEventTypes, ProofsModule, V2ProofProtocol } from '../../../../proofs' import { W3cVcModule } from '../../../../vc' import { customDocumentLoader } from '../../../../vc/__tests__/documentLoader' @@ -104,8 +105,8 @@ const indyJsonLdModules = { registries: [new IndySdkAnonCredsRegistry()], }), dids: new DidsModule({ - resolvers: [new IndySdkSovDidResolver()], - registrars: [new IndySdkSovDidRegistrar()], + resolvers: [new IndySdkSovDidResolver(), new KeyDidResolver()], + registrars: [new IndySdkSovDidRegistrar(), new KeyDidRegistrar()], }), indySdk: new IndySdkModule({ indySdk, @@ -137,14 +138,33 @@ describe('V2 Credentials - JSON-LD - Ed25519', () => { let credentialDefinitionId: string beforeAll(async () => { - const faberAgent = new Agent(getAgentOptions('Faber Agent Indy/JsonLD', {}, indyJsonLdModules)) - const aliceAgent = new Agent(getAgentOptions('Alice Agent Indy/JsonLD', {}, indyJsonLdModules)) + faberAgent = new Agent( + getAgentOptions( + 'Faber Agent Indy/JsonLD', + { + endpoints: ['rxjs:faber'], + }, + indyJsonLdModules + ) + ) + aliceAgent = new Agent( + getAgentOptions( + 'Alice Agent Indy/JsonLD', + { + endpoints: ['rxjs:alice'], + }, + indyJsonLdModules + ) + ) setupSubjectTransports([faberAgent, aliceAgent]) ;[faberReplay, aliceReplay] = setupEventReplaySubjects( [faberAgent, aliceAgent], [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] ) + await faberAgent.initialize() + await aliceAgent.initialize() + ;[, { id: aliceConnectionId }] = await makeConnection(faberAgent, aliceAgent) const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], @@ -197,7 +217,7 @@ describe('V2 Credentials - JSON-LD - Ed25519', () => { state: CredentialState.OfferReceived, }) - const offerMessage = await faberAgent.credentials.findOfferMessage(aliceCredentialRecord.id) + const offerMessage = await aliceAgent.credentials.findOfferMessage(aliceCredentialRecord.id) expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', '@id': expect.any(String), @@ -515,8 +535,7 @@ describe('V2 Credentials - JSON-LD - Ed25519', () => { const credentialMessage = await faberAgent.credentials.findCredentialMessage(faberCredentialRecord.id) const w3cCredential = credentialMessage?.credentialAttachments[1].getDataAsJson() expect(w3cCredential).toMatchObject({ - todo: 'todo', - context: [ + '@context': [ 'https://www.w3.org/2018/credentials/v1', 'https://w3id.org/citizenship/v1', 'https://w3id.org/security/bbs/v1', diff --git a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts index 7fd599fa45..5471920349 100644 --- a/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts @@ -1,6 +1,8 @@ import type { KeyDidCreateOptions } from '../methods/key/KeyDidRegistrar' import type { PeerDidNumAlgo0CreateOptions } from '../methods/peer/PeerDidRegistrar' +import { IndySdkModule } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../tests' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { KeyType } from '../../../crypto' @@ -8,7 +10,15 @@ import { PeerDidNumAlgo } from '../methods/peer/didPeer' import { JsonTransformer } from '@aries-framework/core' -const agentOptions = getAgentOptions('Faber Dids Registrar') +const agentOptions = getAgentOptions( + 'Faber Dids Registrar', + {}, + { + indySdk: new IndySdkModule({ + indySdk, + }), + } +) describe('dids', () => { let agent: Agent diff --git a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts index 8cdf425a48..3e46ada4f0 100644 --- a/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts +++ b/packages/core/src/modules/dids/__tests__/dids-resolver.e2e.test.ts @@ -1,12 +1,23 @@ +import { IndySdkModule } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../tests' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { JsonTransformer } from '../../../utils' -describe('dids', () => { - let agent: Agent +const agent = new Agent( + getAgentOptions( + 'Faber Dids', + {}, + { + indySdk: new IndySdkModule({ + indySdk, + }), + } + ) +) +describe('dids', () => { beforeAll(async () => { - agent = new Agent(getAgentOptions('Faber Dids')) await agent.initialize() }) diff --git a/packages/core/src/modules/dids/services/DidResolverService.ts b/packages/core/src/modules/dids/services/DidResolverService.ts index e23206cc9a..7f97d3f9d1 100644 --- a/packages/core/src/modules/dids/services/DidResolverService.ts +++ b/packages/core/src/modules/dids/services/DidResolverService.ts @@ -46,7 +46,10 @@ export class DidResolverService { if (!resolver) { return { ...result, - didResolutionMetadata: { error: 'unsupportedDidMethod' }, + didResolutionMetadata: { + error: 'unsupportedDidMethod', + message: `No did resolver registered for did method ${parsed.method}`, + }, } } diff --git a/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts b/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts index 81f250f294..00b17ad458 100644 --- a/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts +++ b/packages/core/src/modules/dids/services/__tests__/DidResolverService.test.ts @@ -65,6 +65,7 @@ describe('DidResolverService', () => { didDocumentMetadata: {}, didResolutionMetadata: { error: 'unsupportedDidMethod', + message: 'No did resolver registered for did method example', }, }) }) diff --git a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts index 19e48cd386..a95ff845c7 100644 --- a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts @@ -1,47 +1,49 @@ -import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '../../connections' import type { DiscoverFeaturesDisclosureReceivedEvent, DiscoverFeaturesQueryReceivedEvent, } from '../DiscoverFeaturesEvents' -import { ReplaySubject, Subject } from 'rxjs' +import { ReplaySubject } from 'rxjs' -import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' +import { setupSubjectTransports } from '../../../../tests' import { getAgentOptions, makeConnection } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' +const modules = getIndySdkModules() + +const faberAgentOptions = getAgentOptions( + 'Faber Discover Features V1 E2E', + { + endpoints: ['rxjs:faber'], + }, + modules +) + +const aliceAgentOptions = getAgentOptions( + 'Alice Discover Features V1 E2E', + { + endpoints: ['rxjs:alice'], + }, + modules +) + describe('v1 discover features', () => { let faberAgent: Agent let aliceAgent: Agent let faberConnection: ConnectionRecord beforeAll(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - const faberAgentOptions = getAgentOptions('Faber Discover Features V1 E2E', { - endpoints: ['rxjs:faber'], - }) - - const aliceAgentOptions = getAgentOptions('Alice Discover Features V1 E2E', { - endpoints: ['rxjs:alice'], - }) faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + setupSubjectTransports([faberAgent, aliceAgent]) + + await faberAgent.initialize() await aliceAgent.initialize() ;[faberConnection] = await makeConnection(faberAgent, aliceAgent) }) @@ -53,7 +55,7 @@ describe('v1 discover features', () => { await aliceAgent.wallet.delete() }) - test('Faber asks Alice for issue credential protocol support', async () => { + test('Faber asks Alice for revocation notification protocol support', async () => { const faberReplay = new ReplaySubject() const aliceReplay = new ReplaySubject() @@ -67,14 +69,14 @@ describe('v1 discover features', () => { await faberAgent.discovery.queryFeatures({ connectionId: faberConnection.id, protocolVersion: 'v1', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], }) const query = await waitForQuerySubject(aliceReplay, { timeoutMs: 10000 }) expect(query).toMatchObject({ protocolVersion: 'v1', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], }) const disclosure = await waitForDisclosureSubject(faberReplay, { timeoutMs: 10000 }) @@ -82,24 +84,24 @@ describe('v1 discover features', () => { expect(disclosure).toMatchObject({ protocolVersion: 'v1', disclosures: [ - { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, - { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, ], }) }) - test('Faber asks Alice for issue credential protocol support synchronously', async () => { + test('Faber asks Alice for revocation notification protocol support synchronously', async () => { const matchingFeatures = await faberAgent.discovery.queryFeatures({ connectionId: faberConnection.id, protocolVersion: 'v1', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], awaitDisclosures: true, }) expect(matchingFeatures).toMatchObject({ features: [ - { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, - { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, ], }) }) diff --git a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts index 20e2d72e2b..e9649cdec1 100644 --- a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts @@ -1,14 +1,13 @@ -import type { SubjectMessage } from '../../../../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '../../connections' import type { DiscoverFeaturesDisclosureReceivedEvent, DiscoverFeaturesQueryReceivedEvent, } from '../DiscoverFeaturesEvents' -import { ReplaySubject, Subject } from 'rxjs' +import { ReplaySubject } from 'rxjs' -import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' +import { setupSubjectTransports } from '../../../../tests' import { getAgentOptions, makeConnection } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { GoalCode, Feature } from '../../../agent/models' @@ -16,6 +15,24 @@ import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' +const modules = getIndySdkModules() + +const faberAgentOptions = getAgentOptions( + 'Faber Discover Features V2 E2E', + { + endpoints: ['rxjs:faber'], + }, + modules +) + +const aliceAgentOptions = getAgentOptions( + 'Alice Discover Features V2 E2E', + { + endpoints: ['rxjs:alice'], + }, + modules +) + describe('v2 discover features', () => { let faberAgent: Agent let aliceAgent: Agent @@ -23,27 +40,11 @@ describe('v2 discover features', () => { let faberConnection: ConnectionRecord beforeAll(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - } - const faberAgentOptions = getAgentOptions('Faber Discover Features V2 E2E', { - endpoints: ['rxjs:faber'], - }) - - const aliceAgentOptions = getAgentOptions('Alice Discover Features V2 E2E', { - endpoints: ['rxjs:alice'], - }) faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await faberAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + setupSubjectTransports([faberAgent, aliceAgent]) + + await faberAgent.initialize() await aliceAgent.initialize() ;[faberConnection, aliceConnection] = await makeConnection(faberAgent, aliceAgent) }) @@ -70,14 +71,14 @@ describe('v2 discover features', () => { await faberAgent.discovery.queryFeatures({ connectionId: faberConnection.id, protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], }) const query = await waitForQuerySubject(aliceReplay, { timeoutMs: 10000 }) expect(query).toMatchObject({ protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], }) const disclosure = await waitForDisclosureSubject(faberReplay, { timeoutMs: 10000 }) @@ -85,8 +86,8 @@ describe('v2 discover features', () => { expect(disclosure).toMatchObject({ protocolVersion: 'v2', disclosures: [ - { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, - { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, ], }) }) @@ -220,14 +221,14 @@ describe('v2 discover features', () => { const matchingFeatures = await faberAgent.discovery.queryFeatures({ connectionId: faberConnection.id, protocolVersion: 'v2', - queries: [{ featureType: 'protocol', match: 'https://didcomm.org/issue-credential/*' }], + queries: [{ featureType: 'protocol', match: 'https://didcomm.org/revocation_notification/*' }], awaitDisclosures: true, }) expect(matchingFeatures).toMatchObject({ features: [ - { type: 'protocol', id: 'https://didcomm.org/issue-credential/1.0', roles: ['holder', 'issuer'] }, - { type: 'protocol', id: 'https://didcomm.org/issue-credential/2.0', roles: ['holder', 'issuer'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/1.0', roles: ['holder'] }, + { type: 'protocol', id: 'https://didcomm.org/revocation_notification/2.0', roles: ['holder'] }, ], }) }) diff --git a/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts b/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts index d135c0e9ea..5807e13d70 100644 --- a/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts +++ b/packages/core/src/modules/oob/__tests__/connect-to-self.e2e.test.ts @@ -5,15 +5,21 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions } from '../../../../tests/helpers' import { HandshakeProtocol, DidExchangeState } from '../../connections' -import { OutOfBandState } from '../domain/OutOfBandState' import { Agent } from '@aries-framework/core' -const faberAgentOptions = getAgentOptions('Faber Agent OOB Connect to Self', { - endpoints: ['rxjs:faber'], -}) +import { OutOfBandState } from '../domain/OutOfBandState' + +const faberAgentOptions = getAgentOptions( + 'Faber Agent OOB Connect to Self', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) describe('out of band', () => { let faberAgent: Agent diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts index b7bd3e5d41..18dc989d34 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts @@ -1,8 +1,9 @@ +import type { AnonCredsProofRequest } from '../../../../../../../anoncreds/src/models/exchange' import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' import type { EventReplaySubject } from '../../../../../../tests' import type { V2ProposePresentationMessage, V2RequestPresentationMessage } from '../messages' -import { AnonCredsProofRequest } from '../../../../../../../anoncreds/src/models/AnonCredsProofRequest' +import { AnonCredsProofRequest as AnonCredsProofRequestClass } from '../../../../../../../anoncreds/src/models/AnonCredsProofRequest' import { issueLegacyAnonCredsCredential, setupAnonCredsTests, @@ -94,6 +95,7 @@ describe('V2 Proofs Negotiation - Indy', () => { testLogger.test('Faber waits for presentation from Alice') let faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { state: ProofState.ProposalReceived, + threadId: aliceProofExchangeRecord.threadId, }) const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) @@ -119,23 +121,17 @@ describe('V2 Proofs Negotiation - Indy', () => { }) // eslint-disable-next-line @typescript-eslint/no-explicit-any - const proposalAttach = (proposal as V2ProposePresentationMessage)?.proposalAttachments?.[0].getDataAsJson() + const proposalAttach = ( + proposal as V2ProposePresentationMessage + )?.proposalAttachments?.[0].getDataAsJson() + expect(proposalAttach).toMatchObject({ - requested_attributes: { - something: { - name: 'image_0', - restrictions: [ - { - cred_def_id: credentialDefinitionId, - }, - ], - }, - }, + requested_attributes: {}, requested_predicates: { - something_else: { + [Object.keys(proposalAttach.requested_predicates)[0]]: { name: 'age', p_type: '>=', - p_value: 50, + p_value: 18, restrictions: [ { cred_def_id: credentialDefinitionId, @@ -239,6 +235,9 @@ describe('V2 Proofs Negotiation - Indy', () => { testLogger.test('Faber waits for presentation from Alice') faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { state: ProofState.ProposalReceived, + threadId: aliceProofExchangeRecord.threadId, + // Negotiation so this will be the second proposal + count: 2, }) const proposal2 = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) @@ -263,11 +262,13 @@ describe('V2 Proofs Negotiation - Indy', () => { comment: 'V2 propose proof test 2', }) - const proposalAttach2 = (proposal as V2ProposePresentationMessage)?.proposalAttachments[0].getDataAsJson() + const proposalAttach2 = ( + proposal as V2ProposePresentationMessage + )?.proposalAttachments[0].getDataAsJson() expect(proposalAttach2).toMatchObject({ requested_attributes: {}, requested_predicates: { - something_else: { + [Object.keys(proposalAttach2.requested_predicates)[0]]: { name: 'age', p_type: '>=', p_value: 50, @@ -296,6 +297,8 @@ describe('V2 Proofs Negotiation - Indy', () => { aliceProofExchangeRecord = await waitForProofExchangeRecordSubject(aliceReplay, { threadId: faberProofExchangeRecord.threadId, state: ProofState.RequestReceived, + // Negotiation so this will be the second request + count: 2, }) const request2 = await faberAgent.proofs.findRequestMessage(faberProofExchangeRecord.id) @@ -382,7 +385,7 @@ describe('V2 Proofs Negotiation - Indy', () => { const proofRequest = JsonTransformer.fromJSON( proofRequestMessage.requestAttachments[0].getDataAsJson(), - AnonCredsProofRequest + AnonCredsProofRequestClass ) const predicateKey = proofRequest.requestedPredicates?.keys().next().value diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index f3a9bdb441..b7cf2f9625 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -7,21 +7,32 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' -const recipientAgentOptions = getAgentOptions('Mediation: Recipient') -const mediatorAgentOptions = getAgentOptions('Mediation: Mediator', { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], -}) - -const senderAgentOptions = getAgentOptions('Mediation: Sender', { - endpoints: ['rxjs:sender'], -}) +const modules = getIndySdkModules() + +const recipientAgentOptions = getAgentOptions('Mediation: Recipient', {}, modules) +const mediatorAgentOptions = getAgentOptions( + 'Mediation: Mediator', + { + autoAcceptMediationRequests: true, + endpoints: ['rxjs:mediator'], + }, + modules +) + +const senderAgentOptions = getAgentOptions( + 'Mediation: Sender', + { + endpoints: ['rxjs:sender'], + }, + modules +) describe('mediator establishment', () => { let recipientAgent: Agent @@ -146,27 +157,27 @@ describe('mediator establishment', () => { 6. Send basic message from sender to recipient and assert it is received on the recipient side `, async () => { await e2eMediationTest(mediatorAgentOptions, { + ...recipientAgentOptions, config: { ...recipientAgentOptions.config, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - dependencies: recipientAgentOptions.dependencies, }) }) test('Mediation end-to-end flow (not using did:key)', async () => { await e2eMediationTest( { + ...mediatorAgentOptions, config: { ...mediatorAgentOptions.config, useDidKeyInProtocols: false }, - dependencies: mediatorAgentOptions.dependencies, }, { + ...recipientAgentOptions, config: { ...recipientAgentOptions.config, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, useDidKeyInProtocols: false, }, - dependencies: recipientAgentOptions.dependencies, } ) }) diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts b/packages/core/src/modules/routing/__tests__/pickup.test.ts index 65587fee96..2e12eae8ff 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ b/packages/core/src/modules/routing/__tests__/pickup.test.ts @@ -5,18 +5,28 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions, waitForBasicMessage, waitForTrustPingReceivedEvent } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' -const recipientOptions = getAgentOptions('Mediation: Recipient Pickup', { - autoAcceptConnections: true, -}) -const mediatorOptions = getAgentOptions('Mediation: Mediator Pickup', { - autoAcceptConnections: true, - endpoints: ['wss://mediator'], -}) +const modules = getIndySdkModules() +const recipientOptions = getAgentOptions( + 'Mediation: Recipient Pickup', + { + autoAcceptConnections: true, + }, + modules +) +const mediatorOptions = getAgentOptions( + 'Mediation: Mediator Pickup', + { + autoAcceptConnections: true, + endpoints: ['wss://mediator'], + }, + modules +) describe('E2E Pick Up protocol', () => { let recipientAgent: Agent diff --git a/packages/core/src/storage/migration/__tests__/0.1.test.ts b/packages/core/src/storage/migration/__tests__/0.1.test.ts index ad2dd0b837..63f445ee4a 100644 --- a/packages/core/src/storage/migration/__tests__/0.1.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.1.test.ts @@ -5,6 +5,9 @@ import { unlinkSync, readFileSync } from 'fs' import path from 'path' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../../indy-sdk/src/types' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../../../../src' import { agentDependencies as dependencies } from '../../../../tests/helpers' import { InjectionSymbols } from '../../../constants' @@ -39,6 +42,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -101,6 +107,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -165,6 +174,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -229,6 +241,9 @@ describe('UpdateAssistant | v0.1 - v0.2', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { diff --git a/packages/core/src/storage/migration/__tests__/0.2.test.ts b/packages/core/src/storage/migration/__tests__/0.2.test.ts index 08ed9dce64..e5b49a2ee1 100644 --- a/packages/core/src/storage/migration/__tests__/0.2.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.2.test.ts @@ -4,7 +4,10 @@ import { unlinkSync, readFileSync } from 'fs' import path from 'path' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../../indy-sdk/src/types' import { Agent } from '../../../../src' +import { indySdk } from '../../../../tests' import { agentDependencies } from '../../../../tests/helpers' import { InjectionSymbols } from '../../../constants' import { DependencyManager } from '../../../plugins' @@ -34,6 +37,9 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -106,6 +112,9 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { @@ -156,6 +165,9 @@ describe('UpdateAssistant | v0.2 - v0.3.1', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) const agent = new Agent( diff --git a/packages/core/src/storage/migration/__tests__/0.3.test.ts b/packages/core/src/storage/migration/__tests__/0.3.test.ts index b797fc7c97..65db19ad19 100644 --- a/packages/core/src/storage/migration/__tests__/0.3.test.ts +++ b/packages/core/src/storage/migration/__tests__/0.3.test.ts @@ -4,6 +4,9 @@ import { unlinkSync, readFileSync } from 'fs' import path from 'path' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../../indy-sdk/src/types' +import { indySdk } from '../../../../tests' import { agentDependencies } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' @@ -31,6 +34,9 @@ describe('UpdateAssistant | v0.3 - v0.3.1', () => { const dependencyManager = new DependencyManager() const storageService = new InMemoryStorageService() dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) const agent = new Agent( { diff --git a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts index d1677a5648..2f9e3e80a3 100644 --- a/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts +++ b/packages/core/src/storage/migration/__tests__/UpdateAssistant.test.ts @@ -1,6 +1,9 @@ import type { BaseRecord } from '../../BaseRecord' import { InMemoryStorageService } from '../../../../../../tests/InMemoryStorageService' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { IndySdkSymbol } from '../../../../../indy-sdk/src/types' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' @@ -8,7 +11,7 @@ import { DependencyManager } from '../../../plugins' import { UpdateAssistant } from '../UpdateAssistant' import { CURRENT_FRAMEWORK_STORAGE_VERSION } from '../updates' -const agentOptions = getAgentOptions('UpdateAssistant') +const agentOptions = getAgentOptions('UpdateAssistant', {}) describe('UpdateAssistant', () => { let updateAssistant: UpdateAssistant @@ -18,6 +21,9 @@ describe('UpdateAssistant', () => { beforeEach(async () => { const dependencyManager = new DependencyManager() storageService = new InMemoryStorageService() + // If we register the IndySdkModule it will register the storage service, but we use in memory storage here + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerInstance(IndySdkSymbol, indySdk) dependencyManager.registerInstance(InjectionSymbols.StorageService, storageService) agent = new Agent(agentOptions, dependencyManager) diff --git a/packages/core/src/storage/migration/__tests__/backup.test.ts b/packages/core/src/storage/migration/__tests__/backup.test.ts index 73aba5823f..863e7ca31c 100644 --- a/packages/core/src/storage/migration/__tests__/backup.test.ts +++ b/packages/core/src/storage/migration/__tests__/backup.test.ts @@ -4,6 +4,7 @@ import type { StorageUpdateError } from '../error/StorageUpdateError' import { readFileSync, unlinkSync } from 'fs' import path from 'path' +import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' import { InjectionSymbols } from '../../../constants' @@ -13,7 +14,7 @@ import { JsonTransformer } from '../../../utils' import { StorageUpdateService } from '../StorageUpdateService' import { UpdateAssistant } from '../UpdateAssistant' -const agentOptions = getAgentOptions('UpdateAssistant | Backup') +const agentOptions = getAgentOptions('UpdateAssistant | Backup', {}, getIndySdkModules()) const aliceCredentialRecordsString = readFileSync( path.join(__dirname, '__fixtures__/alice-4-credentials-0.1.json'), diff --git a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts index 647f8e8a40..59dc6e9b88 100644 --- a/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts +++ b/packages/core/src/storage/migration/updates/0.1-0.2/credential.ts @@ -170,7 +170,7 @@ export async function updateIndyMetadata( * "credentials": [ * { * "credentialRecordId": "09e46da9-a575-4909-b016-040e96c3c539", - * "credentialRecordType": "indy", + * "credentialRecordType": "anoncreds" * } * ] * } diff --git a/packages/core/tests/agents.test.ts b/packages/core/tests/agents.test.ts index 47393e371b..5a0f9331ce 100644 --- a/packages/core/tests/agents.test.ts +++ b/packages/core/tests/agents.test.ts @@ -1,22 +1,29 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { ConnectionRecord } from '../src/modules/connections' -import { Subject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { HandshakeProtocol } from '../src/modules/connections' import { waitForBasicMessage, getAgentOptions } from './helpers' - -const aliceAgentOptions = getAgentOptions('Agents Alice', { - endpoints: ['rxjs:alice'], -}) -const bobAgentOptions = getAgentOptions('Agents Bob', { - endpoints: ['rxjs:bob'], -}) +import { setupSubjectTransports } from './transport' + +const modules = getIndySdkModules() + +const aliceAgentOptions = getAgentOptions( + 'Agents Alice', + { + endpoints: ['rxjs:alice'], + }, + modules +) +const bobAgentOptions = getAgentOptions( + 'Agents Bob', + { + endpoints: ['rxjs:bob'], + }, + modules +) describe('agents', () => { let aliceAgent: Agent @@ -32,22 +39,12 @@ describe('agents', () => { }) test('make a connection between agents', async () => { - const aliceMessages = new Subject() - const bobMessages = new Subject() + aliceAgent = new Agent(aliceAgentOptions) + bobAgent = new Agent(bobAgentOptions) - const subjectMap = { - 'rxjs:alice': aliceMessages, - 'rxjs:bob': bobMessages, - } + setupSubjectTransports([aliceAgent, bobAgent]) - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - - bobAgent = new Agent(bobAgentOptions) - bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) - bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await bobAgent.initialize() const aliceBobOutOfBandRecord = await aliceAgent.oob.createInvitation({ diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index 24c03ad907..444375e573 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -1,11 +1,9 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { AgentMessageProcessedEvent, KeylistUpdate } from '../src' -import { filter, firstValueFrom, map, Subject, timeout } from 'rxjs' +import { filter, firstValueFrom, map, timeout } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Key, AgentEventTypes, @@ -19,6 +17,7 @@ import { didKeyToVerkey } from '../src/modules/dids/helpers' import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' import { getAgentOptions, waitForTrustPingResponseReceivedEvent } from './helpers' +import { setupSubjectTransports } from './transport' describe('connections', () => { let faberAgent: Agent @@ -27,50 +26,47 @@ describe('connections', () => { let mediatorAgent: Agent beforeEach(async () => { - const faberAgentOptions = getAgentOptions('Faber Agent Connections', { - endpoints: ['rxjs:faber'], - }) - const aliceAgentOptions = getAgentOptions('Alice Agent Connections', { - endpoints: ['rxjs:alice'], - }) - const acmeAgentOptions = getAgentOptions('Acme Agent Connections', { - endpoints: ['rxjs:acme'], - }) - const mediatorAgentOptions = getAgentOptions('Mediator Agent Connections', { - endpoints: ['rxjs:mediator'], - autoAcceptMediationRequests: true, - }) + const modules = getIndySdkModules() + const faberAgentOptions = getAgentOptions( + 'Faber Agent Connections', + { + endpoints: ['rxjs:faber'], + }, + modules + ) + const aliceAgentOptions = getAgentOptions( + 'Alice Agent Connections', + { + endpoints: ['rxjs:alice'], + }, + modules + ) + const acmeAgentOptions = getAgentOptions( + 'Acme Agent Connections', + { + endpoints: ['rxjs:acme'], + }, + modules + ) + const mediatorAgentOptions = getAgentOptions( + 'Mediator Agent Connections', + { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }, + modules + ) - const faberMessages = new Subject() - const aliceMessages = new Subject() - const acmeMessages = new Subject() - const mediatorMessages = new Subject() + faberAgent = new Agent(faberAgentOptions) + aliceAgent = new Agent(aliceAgentOptions) + acmeAgent = new Agent(acmeAgentOptions) + mediatorAgent = new Agent(mediatorAgentOptions) - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - 'rxjs:acme': acmeMessages, - 'rxjs:mediator': mediatorMessages, - } + setupSubjectTransports([faberAgent, aliceAgent, acmeAgent, mediatorAgent]) - faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await faberAgent.initialize() - - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - - acmeAgent = new Agent(acmeAgentOptions) - acmeAgent.registerInboundTransport(new SubjectInboundTransport(acmeMessages)) - acmeAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await acmeAgent.initialize() - - mediatorAgent = new Agent(mediatorAgentOptions) - mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) - mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await mediatorAgent.initialize() }) diff --git a/packages/core/tests/generic-records.test.ts b/packages/core/tests/generic-records.test.ts index 627fcb6540..3d37def0ed 100644 --- a/packages/core/tests/generic-records.test.ts +++ b/packages/core/tests/generic-records.test.ts @@ -1,13 +1,18 @@ import type { GenericRecord } from '../src/modules/generic-records/repository/GenericRecord' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { RecordNotFoundError } from '../src/error' import { getAgentOptions } from './helpers' -const aliceAgentOptions = getAgentOptions('Generic Records Alice', { - endpoints: ['rxjs:alice'], -}) +const aliceAgentOptions = getAgentOptions( + 'Generic Records Alice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) describe('genericRecords', () => { let aliceAgent: Agent diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index bfc0fe1387..f3c7f7306f 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -21,8 +21,8 @@ import type { Observable } from 'rxjs' import { readFileSync } from 'fs' import path from 'path' -import { firstValueFrom, ReplaySubject } from 'rxjs' -import { catchError, filter, map, timeout } from 'rxjs/operators' +import { lastValueFrom, firstValueFrom, ReplaySubject } from 'rxjs' +import { catchError, filter, map, take, timeout } from 'rxjs/operators' import { agentDependencies, WalletScheme } from '../../node/src' import { @@ -47,6 +47,7 @@ import { OutOfBandState } from '../src/modules/oob/domain/OutOfBandState' import { OutOfBandInvitation } from '../src/modules/oob/messages' import { OutOfBandRecord } from '../src/modules/oob/repository' import { KeyDerivationMethod } from '../src/types' +import { uuid } from '../src/utils/uuid' import testLogger, { TestLogger } from './logger' @@ -66,10 +67,11 @@ export function getAgentOptions = {}, modules?: AgentModules ): { config: InitConfig; modules: AgentModules; dependencies: AgentDependencies } { + const random = uuid().slice(0, 4) const config: InitConfig = { - label: `Agent: ${name}`, + label: `Agent: ${name} - ${random}`, walletConfig: { - id: `Wallet: ${name}`, + id: `Wallet: ${name} - ${random}`, key: 'DZ9hPqFWTPxemcGea72C1X1nusqk5wFNLq6QPjwXGqAa', // generated using indy.generateWalletKey keyDerivationMethod: KeyDerivationMethod.Raw, }, @@ -84,11 +86,17 @@ export function getAgentOptions = {}) { +export function getPostgresAgentOptions( + name: string, + extraConfig: Partial = {}, + modules?: AgentModules +) { + const random = uuid().slice(0, 4) + const config: InitConfig = { label: `Agent: ${name}`, walletConfig: { - id: `Wallet${name}`, + id: `Wallet: ${name} - ${random}`, key: `Key${name}`, storage: { type: 'postgres_storage', @@ -111,7 +119,7 @@ export function getPostgresAgentOptions(name: string, extraConfig: Partial = subject instanceof ReplaySubject ? subject.asObservable() : subject - return firstValueFrom( + return lastValueFrom( observable.pipe( filter(isProofStateChangedEvent), filter((e) => previousState === undefined || e.payload.previousState === previousState), @@ -200,13 +210,14 @@ export function waitForProofExchangeRecordSubject( catchError(() => { throw new Error( `ProofStateChangedEvent event not emitted within specified timeout: ${timeoutMs} - previousState: ${previousState}, - threadId: ${threadId}, - parentThreadId: ${parentThreadId}, - state: ${state} -}` + previousState: ${previousState}, + threadId: ${threadId}, + parentThreadId: ${parentThreadId}, + state: ${state} + }` ) }), + take(count), map((e) => e.payload.proofRecord) ) ) diff --git a/packages/core/tests/jsonld.ts b/packages/core/tests/jsonld.ts index ace2fe5dac..354599cfb3 100644 --- a/packages/core/tests/jsonld.ts +++ b/packages/core/tests/jsonld.ts @@ -118,25 +118,27 @@ export async function setupJsonLdTests< ) ) - const verifierAgent = new Agent( - getAgentOptions( - verifierName ?? 'NOT USED -- NOT INITIALIZED', - { - endpoints: ['rxjs:verifier'], - }, - modules - ) - ) + const verifierAgent = verifierName + ? new Agent( + getAgentOptions( + verifierName, + { + endpoints: ['rxjs:verifier'], + }, + modules + ) + ) + : undefined - setupSubjectTransports([issuerAgent, holderAgent, verifierAgent]) + setupSubjectTransports(verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent]) const [issuerReplay, holderReplay, verifierReplay] = setupEventReplaySubjects( - [issuerAgent, holderAgent, verifierAgent], + verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent], [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] ) await issuerAgent.initialize() await holderAgent.initialize() - if (verifierName) await verifierAgent.initialize() + if (verifierAgent) await verifierAgent.initialize() let issuerHolderConnection: ConnectionRecord | undefined let holderIssuerConnection: ConnectionRecord | undefined @@ -146,7 +148,7 @@ export async function setupJsonLdTests< if (createConnections ?? true) { ;[issuerHolderConnection, holderIssuerConnection] = await makeConnection(issuerAgent, holderAgent) - if (verifierName) { + if (verifierAgent) { ;[holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) } } diff --git a/packages/core/tests/migration.test.ts b/packages/core/tests/migration.test.ts index 120c24e030..fbf05abf3f 100644 --- a/packages/core/tests/migration.test.ts +++ b/packages/core/tests/migration.test.ts @@ -1,11 +1,12 @@ import type { VersionString } from '../src/utils/version' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { UpdateAssistant } from '../src/storage/migration/UpdateAssistant' import { getAgentOptions } from './helpers' -const agentOptions = getAgentOptions('Migration', { publicDidSeed: undefined }) +const agentOptions = getAgentOptions('Migration', { publicDidSeed: undefined }, getIndySdkModules()) describe('migration', () => { test('manually initiating the update assistant to perform an update', async () => { diff --git a/packages/core/tests/multi-protocol-version.test.ts b/packages/core/tests/multi-protocol-version.test.ts index c7197ca36f..454b352f5d 100644 --- a/packages/core/tests/multi-protocol-version.test.ts +++ b/packages/core/tests/multi-protocol-version.test.ts @@ -1,23 +1,31 @@ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { AgentMessageProcessedEvent } from '../src/agent/Events' -import { filter, firstValueFrom, Subject, timeout } from 'rxjs' +import { filter, firstValueFrom, timeout } from 'rxjs' -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { parseMessageType, MessageSender, Dispatcher, AgentMessage, IsValidMessageType } from '../src' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' +import { parseMessageType, MessageSender, AgentMessage, IsValidMessageType } from '../src' import { Agent } from '../src/agent/Agent' import { AgentEventTypes } from '../src/agent/Events' import { OutboundMessageContext } from '../src/agent/models' import { getAgentOptions } from './helpers' - -const aliceAgentOptions = getAgentOptions('Multi Protocol Versions - Alice', { - endpoints: ['rxjs:alice'], -}) -const bobAgentOptions = getAgentOptions('Multi Protocol Versions - Bob', { - endpoints: ['rxjs:bob'], -}) +import { setupSubjectTransports } from './transport' + +const modules = getIndySdkModules() +const aliceAgentOptions = getAgentOptions( + 'Multi Protocol Versions - Alice', + { + endpoints: ['rxjs:alice'], + }, + modules +) +const bobAgentOptions = getAgentOptions( + 'Multi Protocol Versions - Bob', + { + endpoints: ['rxjs:bob'], + }, + modules +) describe('multi version protocols', () => { let aliceAgent: Agent @@ -31,29 +39,15 @@ describe('multi version protocols', () => { }) test('should successfully handle a message with a lower minor version than the currently supported version', async () => { - const aliceMessages = new Subject() - const bobMessages = new Subject() - - const subjectMap = { - 'rxjs:alice': aliceMessages, - 'rxjs:bob': bobMessages, - } - - const mockHandle = jest.fn() - + bobAgent = new Agent(bobAgentOptions) aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + setupSubjectTransports([aliceAgent, bobAgent]) // Register the test handler with the v1.3 version of the message - const dispatcher = aliceAgent.dependencyManager.resolve(Dispatcher) - dispatcher.registerMessageHandler({ supportedMessages: [TestMessageV13], handle: mockHandle }) + const mockHandle = jest.fn() + aliceAgent.dependencyManager.registerMessageHandlers([{ supportedMessages: [TestMessageV13], handle: mockHandle }]) await aliceAgent.initialize() - - bobAgent = new Agent(bobAgentOptions) - bobAgent.registerInboundTransport(new SubjectInboundTransport(bobMessages)) - bobAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await bobAgent.initialize() const { outOfBandInvitation, id } = await aliceAgent.oob.createInvitation() diff --git a/packages/core/tests/oob-mediation-provision.test.ts b/packages/core/tests/oob-mediation-provision.test.ts index abfebc9f14..011a64be7a 100644 --- a/packages/core/tests/oob-mediation-provision.test.ts +++ b/packages/core/tests/oob-mediation-provision.test.ts @@ -1,28 +1,39 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { OutOfBandInvitation } from '../src/modules/oob/messages' -import { Subject } from 'rxjs' - -import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' -import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' import { MediationState, MediatorPickupStrategy } from '../src/modules/routing' import { getAgentOptions, waitForBasicMessage } from './helpers' - -const faberAgentOptions = getAgentOptions('OOB mediation provision - Faber Agent', { - endpoints: ['rxjs:faber'], -}) -const aliceAgentOptions = getAgentOptions('OOB mediation provision - Alice Recipient Agent', { - endpoints: ['rxjs:alice'], - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) -const mediatorAgentOptions = getAgentOptions('OOB mediation provision - Mediator Agent', { - endpoints: ['rxjs:mediator'], - autoAcceptMediationRequests: true, -}) +import { setupSubjectTransports } from './transport' + +const modules = getIndySdkModules() + +const faberAgentOptions = getAgentOptions( + 'OOB mediation provision - Faber Agent', + { + endpoints: ['rxjs:faber'], + }, + modules +) +const aliceAgentOptions = getAgentOptions( + 'OOB mediation provision - Alice Recipient Agent', + { + endpoints: ['rxjs:alice'], + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + modules +) +const mediatorAgentOptions = getAgentOptions( + 'OOB mediation provision - Mediator Agent', + { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }, + modules +) describe('out of band with mediation set up with provision method', () => { const makeConnectionConfig = { @@ -40,32 +51,19 @@ describe('out of band with mediation set up with provision method', () => { let mediatorOutOfBandInvitation: OutOfBandInvitation beforeAll(async () => { - const faberMessages = new Subject() - const aliceMessages = new Subject() - const mediatorMessages = new Subject() - const subjectMap = { - 'rxjs:faber': faberMessages, - 'rxjs:alice': aliceMessages, - 'rxjs:mediator': mediatorMessages, - } - mediatorAgent = new Agent(mediatorAgentOptions) - mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) - mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - await mediatorAgent.initialize() - + aliceAgent = new Agent(aliceAgentOptions) faberAgent = new Agent(faberAgentOptions) - faberAgent.registerInboundTransport(new SubjectInboundTransport(faberMessages)) - faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + + setupSubjectTransports([mediatorAgent, aliceAgent, faberAgent]) + + await mediatorAgent.initialize() + await aliceAgent.initialize() await faberAgent.initialize() - aliceAgent = new Agent(aliceAgentOptions) - aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) - aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) const mediationOutOfBandRecord = await mediatorAgent.oob.createInvitation(makeConnectionConfig) mediatorOutOfBandInvitation = mediationOutOfBandRecord.outOfBandInvitation - await aliceAgent.initialize() let { connectionRecord } = await aliceAgent.oob.receiveInvitation(mediatorOutOfBandInvitation) connectionRecord = await aliceAgent.connections.returnWhenIsConnected(connectionRecord!.id) await aliceAgent.mediationRecipient.provision(connectionRecord!) diff --git a/packages/core/tests/oob-mediation.test.ts b/packages/core/tests/oob-mediation.test.ts index f085b41f88..33388a7a24 100644 --- a/packages/core/tests/oob-mediation.test.ts +++ b/packages/core/tests/oob-mediation.test.ts @@ -7,6 +7,7 @@ import { filter, firstValueFrom, map, Subject, timeout } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { AgentEventTypes } from '../src/agent/Events' import { DidExchangeState, HandshakeProtocol } from '../src/modules/connections' @@ -21,19 +22,33 @@ import { import { getAgentOptions, waitForBasicMessage } from './helpers' -const faberAgentOptions = getAgentOptions('OOB mediation - Faber Agent', { - endpoints: ['rxjs:faber'], -}) -const aliceAgentOptions = getAgentOptions('OOB mediation - Alice Recipient Agent', { - endpoints: ['rxjs:alice'], - // FIXME: discover features returns that we support this protocol, but we don't support all roles - // we should return that we only support the mediator role so we don't have to explicitly declare this - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) -const mediatorAgentOptions = getAgentOptions('OOB mediation - Mediator Agent', { - endpoints: ['rxjs:mediator'], - autoAcceptMediationRequests: true, -}) +const modules = getIndySdkModules() + +const faberAgentOptions = getAgentOptions( + 'OOB mediation - Faber Agent', + { + endpoints: ['rxjs:faber'], + }, + modules +) +const aliceAgentOptions = getAgentOptions( + 'OOB mediation - Alice Recipient Agent', + { + endpoints: ['rxjs:alice'], + // FIXME: discover features returns that we support this protocol, but we don't support all roles + // we should return that we only support the mediator role so we don't have to explicitly declare this + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + modules +) +const mediatorAgentOptions = getAgentOptions( + 'OOB mediation - Mediator Agent', + { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }, + modules +) describe('out of band with mediation', () => { const makeConnectionConfig = { diff --git a/packages/core/tests/postgres.e2e.test.ts b/packages/core/tests/postgres.e2e.test.ts index eedffe43b5..63dbd018f9 100644 --- a/packages/core/tests/postgres.e2e.test.ts +++ b/packages/core/tests/postgres.e2e.test.ts @@ -7,18 +7,27 @@ import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { loadPostgresPlugin, WalletScheme } from '../../node/src' import { Agent } from '../src/agent/Agent' import { HandshakeProtocol } from '../src/modules/connections' import { waitForBasicMessage, getPostgresAgentOptions } from './helpers' -const alicePostgresAgentOptions = getPostgresAgentOptions('AgentsAlice', { - endpoints: ['rxjs:alice'], -}) -const bobPostgresAgentOptions = getPostgresAgentOptions('AgentsBob', { - endpoints: ['rxjs:bob'], -}) +const alicePostgresAgentOptions = getPostgresAgentOptions( + 'AgentsAlice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) +const bobPostgresAgentOptions = getPostgresAgentOptions( + 'AgentsBob', + { + endpoints: ['rxjs:bob'], + }, + getIndySdkModules() +) describe('postgres agents', () => { let aliceAgent: Agent diff --git a/packages/core/tests/wallet.test.ts b/packages/core/tests/wallet.test.ts index 3362741146..13856dd8de 100644 --- a/packages/core/tests/wallet.test.ts +++ b/packages/core/tests/wallet.test.ts @@ -1,6 +1,7 @@ import { tmpdir } from 'os' import path from 'path' +import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' import { Agent } from '../src/agent/Agent' import { BasicMessageRepository, BasicMessageRecord, BasicMessageRole } from '../src/modules/basic-messages' import { KeyDerivationMethod } from '../src/types' @@ -11,8 +12,10 @@ import { WalletNotFoundError } from '../src/wallet/error/WalletNotFoundError' import { getAgentOptions } from './helpers' -const aliceAgentOptions = getAgentOptions('wallet-tests-Alice') -const bobAgentOptions = getAgentOptions('wallet-tests-Bob') +const modules = getIndySdkModules() + +const aliceAgentOptions = getAgentOptions('wallet-tests-Alice', {}, modules) +const bobAgentOptions = getAgentOptions('wallet-tests-Bob', {}, modules) describe('wallet', () => { let aliceAgent: Agent diff --git a/packages/indy-sdk/src/IndySdkModule.ts b/packages/indy-sdk/src/IndySdkModule.ts index a0858c23bb..dc5640a024 100644 --- a/packages/indy-sdk/src/IndySdkModule.ts +++ b/packages/indy-sdk/src/IndySdkModule.ts @@ -6,7 +6,7 @@ import { AnonCredsIssuerServiceSymbol, AnonCredsVerifierServiceSymbol, } from '@aries-framework/anoncreds' -import { InjectionSymbols } from '@aries-framework/core' +import { AriesFrameworkError, InjectionSymbols } from '@aries-framework/core' import { IndySdkModuleConfig } from './IndySdkModuleConfig' import { IndySdkHolderService, IndySdkIssuerService, IndySdkVerifierService } from './anoncreds' @@ -28,10 +28,20 @@ export class IndySdkModule implements Module { // Register config dependencyManager.registerInstance(IndySdkModuleConfig, this.config) + if (dependencyManager.isRegistered(InjectionSymbols.Wallet)) { + throw new AriesFrameworkError('There is an instance of Wallet already registered') + } else { + dependencyManager.registerContextScoped(InjectionSymbols.Wallet, IndySdkWallet) + } + + if (dependencyManager.isRegistered(InjectionSymbols.StorageService)) { + throw new AriesFrameworkError('There is an instance of StorageService already registered') + } else { + dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndySdkStorageService) + } + // NOTE: for now we are registering the needed indy services. We may want to make this // more explicit and require the user to register the services they need on the specific modules. - dependencyManager.registerSingleton(InjectionSymbols.Wallet, IndySdkWallet) - dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndySdkStorageService) dependencyManager.registerSingleton(AnonCredsIssuerServiceSymbol, IndySdkIssuerService) dependencyManager.registerSingleton(AnonCredsHolderServiceSymbol, IndySdkHolderService) dependencyManager.registerSingleton(AnonCredsVerifierServiceSymbol, IndySdkVerifierService) diff --git a/packages/indy-sdk/tests/setupIndySdkModule.ts b/packages/indy-sdk/tests/setupIndySdkModule.ts index 949a08bccf..12e528b715 100644 --- a/packages/indy-sdk/tests/setupIndySdkModule.ts +++ b/packages/indy-sdk/tests/setupIndySdkModule.ts @@ -1,4 +1,4 @@ -import { DidsModule, utils } from '@aries-framework/core' +import { DidsModule, KeyDidRegistrar, KeyDidResolver, utils } from '@aries-framework/core' import indySdk from 'indy-sdk' import { genesisPath, taaVersion, taaAcceptanceMechanism } from '../../core/tests/helpers' @@ -20,7 +20,7 @@ export const getIndySdkModules = () => ({ ], }), dids: new DidsModule({ - registrars: [new IndySdkSovDidRegistrar()], - resolvers: [new IndySdkSovDidResolver()], + registrars: [new IndySdkSovDidRegistrar(), new KeyDidRegistrar()], + resolvers: [new IndySdkSovDidResolver(), new KeyDidResolver()], }), }) diff --git a/packages/tenants/src/__tests__/TenantsApi.test.ts b/packages/tenants/src/__tests__/TenantsApi.test.ts index e2c5c28fed..213e0cfda3 100644 --- a/packages/tenants/src/__tests__/TenantsApi.test.ts +++ b/packages/tenants/src/__tests__/TenantsApi.test.ts @@ -1,6 +1,7 @@ import { Agent, AgentContext, InjectionSymbols } from '@aries-framework/core' -import { getAgentContext, getAgentOptions, mockFunction } from '../../../core/tests/helpers' +import { indySdk, getAgentContext, getAgentOptions, mockFunction } from '../../../core/tests' +import { IndySdkModule } from '../../../indy-sdk/src' import { TenantAgent } from '../TenantAgent' import { TenantsApi } from '../TenantsApi' import { TenantAgentContextProvider } from '../context/TenantAgentContextProvider' @@ -15,7 +16,7 @@ const AgentContextProviderMock = TenantAgentContextProvider as jest.Mock Date: Fri, 17 Feb 2023 20:49:23 +0100 Subject: [PATCH 16/22] the end is here! Signed-off-by: Timo Glastra --- .../credentials/CredentialsModuleConfig.ts | 4 +- .../core/src/modules/proofs/ProofsModule.ts | 5 +- .../src/modules/proofs/ProofsModuleConfig.ts | 6 +- .../proofs/__tests__/ProofsModule.test.ts | 2 +- .../v2/__tests__/V2ProofProtocol.test.ts | 5 +- .../v2-indy-connectionless-proofs.e2e.test.ts | 18 +++--- .../v2-indy-proof-negotiation.test.ts | 34 +++-------- .../v2-indy-proofs-auto-accept.2e.test.ts | 4 ++ .../v2/__tests__/v2-indy-proofs.e2e.test.ts | 56 ++++++++++--------- 9 files changed, 66 insertions(+), 68 deletions(-) diff --git a/packages/core/src/modules/credentials/CredentialsModuleConfig.ts b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts index 34c20f50e7..e6d23909ed 100644 --- a/packages/core/src/modules/credentials/CredentialsModuleConfig.ts +++ b/packages/core/src/modules/credentials/CredentialsModuleConfig.ts @@ -18,11 +18,11 @@ export interface CredentialsModuleConfigOptions } diff --git a/packages/core/src/modules/proofs/ProofsModuleConfig.ts b/packages/core/src/modules/proofs/ProofsModuleConfig.ts index 3526e4eb7d..e87966ef27 100644 --- a/packages/core/src/modules/proofs/ProofsModuleConfig.ts +++ b/packages/core/src/modules/proofs/ProofsModuleConfig.ts @@ -18,11 +18,11 @@ export interface ProofsModuleConfigOptions { return this.options.autoAcceptProofs ?? AutoAcceptProof.Never } - /** See {@link CredentialsModuleConfigOptions.proofProtocols} */ + /** See {@link ProofsModuleConfigOptions.proofProtocols} */ public get proofProtocols() { return this.options.proofProtocols } diff --git a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts index 121226d9e4..1126aeda52 100644 --- a/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts +++ b/packages/core/src/modules/proofs/__tests__/ProofsModule.test.ts @@ -33,7 +33,7 @@ describe('ProofsModule', () => { expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(ProofRepository) }) - test('registers V1ProofProtocol and V2ProofProtocol if no proofProtocols are configured', () => { + test('registers V2ProofProtocol if no proofProtocols are configured', () => { const proofsModule = new ProofsModule() expect(proofsModule.config.proofProtocols).toEqual([expect.any(V2ProofProtocol)]) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts index 7a90b562c8..3c140bd867 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/V2ProofProtocol.test.ts @@ -33,7 +33,10 @@ const didCommMessageRepositoryMock = DidCommMessageRepository as jest.Mock as ProofFormatService +const proofFormatService = { + supportsFormat: () => true, + processRequest: jest.fn(), +} as unknown as ProofFormatService const agentConfig = getAgentConfig('V2ProofProtocolTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts index fb8761fa3e..34b37e21c6 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -252,11 +252,19 @@ describe('V2 Connectionless Proofs - Indy', () => { const unique = uuid().substring(0, 4) - const mediatorOptions = getAgentOptions(`Connectionless proofs with mediator Mediator-${unique}`, { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], + const anonCredsModules = getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, }) + const mediatorOptions = getAgentOptions( + `Connectionless proofs with mediator Mediator-${unique}`, + { + autoAcceptMediationRequests: true, + endpoints: ['rxjs:mediator'], + }, + anonCredsModules + ) + const mediatorMessages = new Subject() const subjectMap = { 'rxjs:mediator': mediatorMessages } @@ -276,10 +284,6 @@ describe('V2 Connectionless Proofs - Indy', () => { handshakeProtocols: [HandshakeProtocol.Connections], }) - const anonCredsModules = getLegacyAnonCredsModules({ - autoAcceptProofs: AutoAcceptProof.Always, - }) - const faberOptions = getAgentOptions( `Connectionless proofs with mediator Faber-${unique}`, { diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts index 18dc989d34..6bed06c5ab 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proof-negotiation.test.ts @@ -84,7 +84,7 @@ describe('V2 Proofs Negotiation - Indy', () => { credentialDefinitionId, name: 'age', predicate: '>=', - threshold: 18, + threshold: 50, }, ], }, @@ -131,7 +131,7 @@ describe('V2 Proofs Negotiation - Indy', () => { [Object.keys(proposalAttach.requested_predicates)[0]]: { name: 'age', p_type: '>=', - p_value: 18, + p_value: 50, restrictions: [ { cred_def_id: credentialDefinitionId, @@ -224,7 +224,7 @@ describe('V2 Proofs Negotiation - Indy', () => { credentialDefinitionId, name: 'age', predicate: '>=', - threshold: 18, + threshold: 50, }, ], }, @@ -353,20 +353,13 @@ describe('V2 Proofs Negotiation - Indy', () => { comment: 'V2 propose proof test 2', }) - const proposalAttach3 = (proposal as V2ProposePresentationMessage)?.proposalAttachments[0].getDataAsJson() + const proposalAttach3 = ( + proposal as V2ProposePresentationMessage + )?.proposalAttachments[0].getDataAsJson() expect(proposalAttach3).toMatchObject({ - requested_attributes: { - something: { - name: 'name', - restrictions: [ - { - cred_def_id: credentialDefinitionId, - }, - ], - }, - }, + requested_attributes: {}, requested_predicates: { - somethingElse: { + [Object.keys(proposalAttach3.requested_predicates ?? {})[0]]: { name: 'age', p_type: '>=', p_value: 50, @@ -393,16 +386,7 @@ describe('V2 Proofs Negotiation - Indy', () => { name: 'proof-request', nonce: expect.any(String), version: '1.0', - requested_attributes: { - '0': { - name: 'name', - restrictions: [ - { - cred_def_id: credentialDefinitionId, - }, - ], - }, - }, + requested_attributes: {}, requested_predicates: { [predicateKey]: { name: 'age', diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts index 45a63b55a5..06ddf5cc34 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs-auto-accept.2e.test.ts @@ -21,7 +21,9 @@ describe('Auto accept present proof', () => { beforeAll(async () => { ;({ issuerAgent: faberAgent, + issuerReplay: faberReplay, holderAgent: aliceAgent, + holderReplay: aliceReplay, credentialDefinitionId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, @@ -148,7 +150,9 @@ describe('Auto accept present proof', () => { testLogger.test('Initializing the agents') ;({ issuerAgent: faberAgent, + issuerReplay: faberReplay, holderAgent: aliceAgent, + holderReplay: aliceReplay, credentialDefinitionId, issuerHolderConnectionId: faberConnectionId, holderIssuerConnectionId: aliceConnectionId, diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts index 7218a06fac..29a7fce4c8 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-proofs.e2e.test.ts @@ -7,6 +7,8 @@ import { } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' import { waitForProofExchangeRecord } from '../../../../../../tests' import testLogger from '../../../../../../tests/logger' +import { Attachment, AttachmentData } from '../../../../../decorators/attachment/Attachment' +import { LinkedAttachment } from '../../../../../utils/LinkedAttachment' import { ProofState } from '../../../models' import { ProofExchangeRecord } from '../../../repository' import { V2ProposePresentationMessage, V2RequestPresentationMessage, V2PresentationMessage } from '../messages' @@ -36,7 +38,7 @@ describe('Present Proof', () => { } = await setupAnonCredsTests({ issuerName: 'Faber agent indy proofs', holderName: 'Alice agent indy proofs', - attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], + attributeNames: ['name', 'age', 'image_0', 'image_1'], })) await issueLegacyAnonCredsCredential({ @@ -50,21 +52,29 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - value: 'Alice', + value: 'John', }, { name: 'age', - value: '22', - }, - { - name: 'profile_picture', - value: 'https://example.com/alice.jpg', - }, - { - name: 'x-ray', - value: 'https://example.com/alice-xray.jpg', + value: '99', }, ], + linkedAttachments: [ + new LinkedAttachment({ + name: 'image_0', + attachment: new Attachment({ + filename: 'picture-of-a-cat.png', + data: new AttachmentData({ base64: 'cGljdHVyZSBvZiBhIGNhdA==' }), + }), + }), + new LinkedAttachment({ + name: 'image_1', + attachment: new Attachment({ + filename: 'picture-of-a-dog.png', + data: new AttachmentData({ base64: 'UGljdHVyZSBvZiBhIGRvZw==' }), + }), + }), + ], }, }) }) @@ -103,7 +113,7 @@ describe('Present Proof', () => { { name: 'age', predicate: '>=', - threshold: 18, + threshold: 50, credentialDefinitionId, }, ], @@ -280,11 +290,8 @@ describe('Present Proof', () => { version: '1.0', nonce: expect.any(String), requested_attributes: { - 0: { + [Object.keys(formatData.proposal?.indy?.requested_attributes ?? {})[0]]: { name: 'name', - }, - anything: { - name: 'image_0', restrictions: [ { cred_def_id: credentialDefinitionId, @@ -293,7 +300,7 @@ describe('Present Proof', () => { }, }, requested_predicates: { - else: { + [Object.keys(formatData.proposal?.indy?.requested_predicates ?? {})[0]]: { name: 'age', p_type: '>=', p_value: 50, @@ -312,11 +319,8 @@ describe('Present Proof', () => { version: '1.0', nonce: expect.any(String), requested_attributes: { - 0: { + [Object.keys(formatData.request?.indy?.requested_attributes ?? {})[0]]: { name: 'name', - }, - anything: { - name: 'image_0', restrictions: [ { cred_def_id: credentialDefinitionId, @@ -325,7 +329,7 @@ describe('Present Proof', () => { }, }, requested_predicates: { - else: { + [Object.keys(formatData.request?.indy?.requested_predicates ?? {})[0]]: { name: 'age', p_type: '>=', p_value: 50, @@ -395,7 +399,7 @@ describe('Present Proof', () => { age: { name: 'age', p_type: '>=', - p_value: 18, + p_value: 50, restrictions: [ { cred_def_id: credentialDefinitionId, @@ -588,7 +592,7 @@ describe('Present Proof', () => { credentialId: expect.any(String), revealed: true, credentialInfo: { - referent: expect.any(String), + credentialId: expect.any(String), attributes: { image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', image_1: 'hl:zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', @@ -607,7 +611,7 @@ describe('Present Proof', () => { credentialId: expect.any(String), revealed: true, credentialInfo: { - referent: expect.any(String), + credentialId: expect.any(String), attributes: { age: '99', image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', @@ -627,7 +631,7 @@ describe('Present Proof', () => { { credentialId: expect.any(String), credentialInfo: { - referent: expect.any(String), + credentialId: expect.any(String), attributes: { image_1: 'hl:zQmRHBT9rDs5QhsnYuPY3mNpXxgLcnNXkhjWJvTSAPMmcVd', image_0: 'hl:zQmfDXo7T3J43j3CTkEZaz7qdHuABhWktksZ7JEBueZ5zUS', From f90e8147d99c23c1596f17af0feae9531fa1bda3 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 17 Feb 2023 20:53:25 +0100 Subject: [PATCH 17/22] i lied Signed-off-by: Timo Glastra --- .../core/src/agent/__tests__/Agent.test.ts | 2 ++ .../extension-module/tests/dummy.e2e.test.ts | 23 +++++++++---------- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 6402a898b0..9cc1d829ee 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -241,6 +241,8 @@ describe('Agent', () => { 'https://didcomm.org/basicmessage/1.0', 'https://didcomm.org/connections/1.0', 'https://didcomm.org/coordinate-mediation/1.0', + 'https://didcomm.org/issue-credential/2.0', + 'https://didcomm.org/present-proof/2.0', 'https://didcomm.org/didexchange/1.0', 'https://didcomm.org/discover-features/1.0', 'https://didcomm.org/discover-features/2.0', diff --git a/samples/extension-module/tests/dummy.e2e.test.ts b/samples/extension-module/tests/dummy.e2e.test.ts index c9aa891d02..4ae3b51069 100644 --- a/samples/extension-module/tests/dummy.e2e.test.ts +++ b/samples/extension-module/tests/dummy.e2e.test.ts @@ -4,8 +4,10 @@ import type { ConnectionRecord } from '@aries-framework/core' import { Agent } from '@aries-framework/core' import { Subject } from 'rxjs' +import { indySdk } from '../../../packages/core/tests' import { getAgentOptions, makeConnection } from '../../../packages/core/tests/helpers' import testLogger from '../../../packages/core/tests/logger' +import { IndySdkModule } from '../../../packages/indy-sdk/src' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' import { DummyModule } from '../dummy/DummyModule' @@ -13,14 +15,17 @@ import { DummyState } from '../dummy/repository' import { waitForDummyRecord } from './helpers' +const modules = { + dummy: new DummyModule(), + indySdk: new IndySdkModule({ indySdk }), +} + const bobAgentOptions = getAgentOptions( 'Bob Dummy', { endpoints: ['rxjs:bob'], }, - { - dummy: new DummyModule(), - } + modules ) const aliceAgentOptions = getAgentOptions( @@ -28,18 +33,12 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - { - dummy: new DummyModule(), - } + modules ) describe('Dummy extension module test', () => { - let bobAgent: Agent<{ - dummy: DummyModule - }> - let aliceAgent: Agent<{ - dummy: DummyModule - }> + let bobAgent: Agent + let aliceAgent: Agent let aliceConnection: ConnectionRecord beforeEach(async () => { From 3dd4def4a8268816d7ccff0ccccbfc21d0d6849b Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 17 Feb 2023 22:47:32 +0100 Subject: [PATCH 18/22] style: eslint fixes Signed-off-by: Timo Glastra --- .../AnonCredsRsHolderService.test.ts | 2 +- .../__tests__/AnonCredsRsServices.test.ts | 2 +- packages/anoncreds/src/AnonCredsApi.ts | 1 - packages/anoncreds/src/models/exchange.ts | 1 + .../v1-connectionless-proofs.e2e.test.ts | 12 +-- .../anoncreds/tests/legacyAnonCredsSetup.ts | 38 +++----- packages/askar/src/wallet/AskarWallet.ts | 8 +- .../core/src/agent/__tests__/Agent.test.ts | 2 +- .../__tests__/basic-messages.e2e.test.ts | 6 +- .../__tests__/connection-manual.e2e.test.ts | 8 +- .../services/RevocationNotificationService.ts | 12 +-- .../RevocationNotificationService.test.ts | 2 +- .../V2CredentialProtocolOffer.test.ts | 8 +- ...f.credentials.propose-offerED25519.test.ts | 95 ++++++++++--------- .../v1-discover-features.e2e.test.ts | 6 +- .../v2-discover-features.e2e.test.ts | 6 +- .../v2-indy-connectionless-proofs.e2e.test.ts | 16 ++-- .../routing/__tests__/mediation.test.ts | 8 +- .../modules/routing/__tests__/pickup.test.ts | 5 +- .../__tests__/__snapshots__/0.1.test.ts.snap | 16 ---- packages/core/tests/agents.test.ts | 6 +- packages/core/tests/connections.test.ts | 9 +- packages/core/tests/indySdk.ts | 2 +- .../core/tests/multi-protocol-version.test.ts | 5 +- .../tests/oob-mediation-provision.test.ts | 8 +- packages/core/tests/oob-mediation.test.ts | 8 +- packages/core/tests/oob.test.ts | 14 +-- packages/core/tests/wallet.test.ts | 6 +- .../indy-sdk-anoncreds-registry.e2e.test.ts | 4 +- packages/indy-sdk/tests/setupIndySdkModule.ts | 27 +++--- .../tests/sov-did-registrar.e2e.test.ts | 4 +- tests/e2e-http.test.ts | 16 ++-- tests/e2e-subject.test.ts | 16 ++-- tests/e2e-ws-pickup-v2.test.ts | 19 ++-- tests/e2e-ws.test.ts | 16 ++-- 35 files changed, 188 insertions(+), 226 deletions(-) diff --git a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsHolderService.test.ts b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsHolderService.test.ts index bdfac8c48a..23565f8e2b 100644 --- a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsHolderService.test.ts +++ b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsHolderService.test.ts @@ -240,7 +240,7 @@ describe('AnonCredsRsHolderService', () => { }, } - const proof = await anonCredsHolderService.createProof(agentContext, { + await anonCredsHolderService.createProof(agentContext, { credentialDefinitions: { 'personcreddef:uri': personCredentialDefinition as AnonCredsCredentialDefinition, 'phonecreddef:uri': phoneCredentialDefinition as AnonCredsCredentialDefinition, diff --git a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts index f881d22fa3..019063bcbb 100644 --- a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts +++ b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts @@ -57,7 +57,7 @@ describe('AnonCredsRsServices', () => { version: '1.0.0', }) - const { schemaState, schemaMetadata } = await registry.registerSchema(agentContext, { + const { schemaState } = await registry.registerSchema(agentContext, { schema, options: {}, }) diff --git a/packages/anoncreds/src/AnonCredsApi.ts b/packages/anoncreds/src/AnonCredsApi.ts index c19e537a8b..9e56a51ea5 100644 --- a/packages/anoncreds/src/AnonCredsApi.ts +++ b/packages/anoncreds/src/AnonCredsApi.ts @@ -2,7 +2,6 @@ import type { AnonCredsCreateLinkSecretOptions, AnonCredsRegisterCredentialDefinitionOptions, } from './AnonCredsApiOptions' -import type { AnonCredsCredentialDefinition } from './models' import type { GetCredentialDefinitionReturn, GetRevocationStatusListReturn, diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 7ec87b9ec7..82c76119c2 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -88,6 +88,7 @@ export interface AnonCredsProof { requested_predicates: Record } + // TODO: extend types for proof property proof: any identifiers: Array<{ schema_id: string diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts index a57cc84cb0..df66a6217d 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -272,10 +272,6 @@ describe('V1 Proofs - Connectionless - Indy', () => { mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) await mediatorAgent.initialize() - const anonCredsModules = getLegacyAnonCredsModules({ - autoAcceptProofs: AutoAcceptProof.Always, - }) - const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ label: 'faber invitation', handshakeProtocols: [HandshakeProtocol.Connections], @@ -294,7 +290,9 @@ describe('V1 Proofs - Connectionless - Indy', () => { }), mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - anonCredsModules + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) ) const aliceAgentOptions = getAgentOptions( @@ -305,7 +303,9 @@ describe('V1 Proofs - Connectionless - Indy', () => { }), mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - anonCredsModules + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) ) const faberAgent = new Agent(faberAgentOptions) diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 687447e6b6..e5a1082b64 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -27,15 +27,11 @@ import { DidsModule, } from '@aries-framework/core' import { randomUUID } from 'crypto' -import indySdk from 'indy-sdk' import { setupSubjectTransports, setupEventReplaySubjects } from '../../core/tests' import { - genesisPath, getAgentOptions, makeConnection, - taaAcceptanceMechanism, - taaVersion, waitForCredentialRecordSubject, waitForProofExchangeRecordSubject, } from '../../core/tests/helpers' @@ -46,6 +42,7 @@ import { IndySdkSovDidRegistrar, IndySdkSovDidResolver, } from '../../indy-sdk/src' +import { getIndySdkModuleConfig } from '../../indy-sdk/tests/setupIndySdkModule' import { V1CredentialProtocol, V1ProofProtocol, @@ -91,18 +88,7 @@ export const getLegacyAnonCredsModules = ({ resolvers: [new IndySdkSovDidResolver()], registrars: [new IndySdkSovDidRegistrar()], }), - indySdk: new IndySdkModule({ - indySdk, - networks: [ - { - isProduction: false, - genesisPath, - id: randomUUID(), - indyNamespace: `pool:localtest`, - transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, - }, - ], - }), + indySdk: new IndySdkModule(getIndySdkModuleConfig()), cache: new CacheModule({ cache: new InMemoryLruCache({ limit: 100 }), }), @@ -292,18 +278,16 @@ export async function setupAnonCredsTests< attributeNames: string[] createConnections?: CreateConnections }): Promise> { - const modules = getLegacyAnonCredsModules({ - autoAcceptCredentials, - autoAcceptProofs, - }) - const issuerAgent = new Agent( getAgentOptions( issuerName, { endpoints: ['rxjs:issuer'], }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) ) ) @@ -313,7 +297,10 @@ export async function setupAnonCredsTests< { endpoints: ['rxjs:holder'], }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) ) ) @@ -324,7 +311,10 @@ export async function setupAnonCredsTests< { endpoints: ['rxjs:verifier'], }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) ) ) : undefined diff --git a/packages/askar/src/wallet/AskarWallet.ts b/packages/askar/src/wallet/AskarWallet.ts index baa17838a4..af94f14eea 100644 --- a/packages/askar/src/wallet/AskarWallet.ts +++ b/packages/askar/src/wallet/AskarWallet.ts @@ -2,13 +2,11 @@ import type { EncryptedMessage, WalletConfig, WalletCreateKeyOptions, - DidConfig, DidInfo, WalletSignOptions, UnpackedMessageContext, WalletVerifyOptions, Wallet, - WalletExportImportConfig, WalletConfigRekey, KeyPair, KeyDerivationMethod, @@ -302,12 +300,12 @@ export class AskarWallet implements Wallet { } } - public async export(exportConfig: WalletExportImportConfig) { + public async export() { // TODO throw new WalletError('AskarWallet Export not yet implemented') } - public async import(walletConfig: WalletConfig, importConfig: WalletExportImportConfig) { + public async import() { // TODO throw new WalletError('AskarWallet Import not yet implemented') } @@ -338,7 +336,7 @@ export class AskarWallet implements Wallet { } } - public async initPublicDid(didConfig: DidConfig) { + public async initPublicDid() { // Not implemented, as it does not work with legacy Ledger module } diff --git a/packages/core/src/agent/__tests__/Agent.test.ts b/packages/core/src/agent/__tests__/Agent.test.ts index 9cc1d829ee..7423f35c3c 100644 --- a/packages/core/src/agent/__tests__/Agent.test.ts +++ b/packages/core/src/agent/__tests__/Agent.test.ts @@ -253,6 +253,6 @@ describe('Agent', () => { 'https://didcomm.org/revocation_notification/2.0', ]) ) - expect(protocols.length).toEqual(11) + expect(protocols.length).toEqual(13) }) }) diff --git a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts index 21d80883e1..1ed5be6fb7 100644 --- a/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts +++ b/packages/core/src/modules/basic-messages/__tests__/basic-messages.e2e.test.ts @@ -14,14 +14,12 @@ import { MessageSendingError, RecordNotFoundError } from '../../../error' import { BasicMessage } from '../messages' import { BasicMessageRecord } from '../repository' -const modules = getIndySdkModules() - const faberConfig = getAgentOptions( 'Faber Basic Messages', { endpoints: ['rxjs:faber'], }, - modules + getIndySdkModules() ) const aliceConfig = getAgentOptions( @@ -29,7 +27,7 @@ const aliceConfig = getAgentOptions( { endpoints: ['rxjs:alice'], }, - modules + getIndySdkModules() ) describe('Basic Messages E2E', () => { diff --git a/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts index b6dfe6a813..457e5f7b7e 100644 --- a/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts +++ b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts @@ -41,8 +41,6 @@ function waitForResponse(agent: Agent, connectionId: string) { ) } -const modules = getIndySdkModules() - describe('Manual Connection Flow', () => { // This test was added to reproduce a bug where all connections based on a reusable invitation would use the same keys // This was only present in the manual flow, which is almost never used. @@ -54,7 +52,7 @@ describe('Manual Connection Flow', () => { autoAcceptConnections: false, endpoints: ['rxjs:alice'], }, - modules + getIndySdkModules() ) const bobAgentOptions = getAgentOptions( 'Manual Connection Flow Bob', @@ -63,7 +61,7 @@ describe('Manual Connection Flow', () => { autoAcceptConnections: false, endpoints: ['rxjs:bob'], }, - modules + getIndySdkModules() ) const faberAgentOptions = getAgentOptions( 'Manual Connection Flow Faber', @@ -71,7 +69,7 @@ describe('Manual Connection Flow', () => { autoAcceptConnections: false, endpoints: ['rxjs:faber'], }, - modules + getIndySdkModules() ) const aliceAgent = new Agent(aliceAgentOptions) diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts index f80a080677..e2b9d6e1f9 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts @@ -89,14 +89,14 @@ export class RevocationNotificationService { ) } - const [, , indyRevocationRegistryId, indyCredentialRevocationId] = threadIdGroups + const [, , anonCredsRevocationRegistryId, anonCredsCredentialRevocationId] = threadIdGroups const comment = messageContext.message.comment const connection = messageContext.assertReadyConnection() await this.processRevocationNotification( messageContext.agentContext, - indyRevocationRegistryId, - indyCredentialRevocationId, + anonCredsRevocationRegistryId, + anonCredsCredentialRevocationId, connection, comment ) @@ -132,13 +132,13 @@ export class RevocationNotificationService { ) } - const [, indyRevocationRegistryId, indyCredentialRevocationId] = credentialIdGroups + const [, anonCredsRevocationRegistryId, anonCredsCredentialRevocationId] = credentialIdGroups const comment = messageContext.message.comment const connection = messageContext.assertReadyConnection() await this.processRevocationNotification( messageContext.agentContext, - indyRevocationRegistryId, - indyCredentialRevocationId, + anonCredsRevocationRegistryId, + anonCredsCredentialRevocationId, connection, comment ) diff --git a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts index 5e864104af..e834ca5585 100644 --- a/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts +++ b/packages/core/src/modules/credentials/protocol/revocation-notification/services/__tests__/RevocationNotificationService.test.ts @@ -1,3 +1,4 @@ +import type { AnonCredsCredentialMetadata } from '../../../../../../../../anoncreds/src/utils/metadata' import type { AgentContext } from '../../../../../../agent' import type { RevocationNotificationReceivedEvent } from '../../../../CredentialEvents' @@ -12,7 +13,6 @@ import { CredentialEventTypes } from '../../../../CredentialEvents' import { CredentialRepository } from '../../../../repository/CredentialRepository' import { V1RevocationNotificationMessage, V2RevocationNotificationMessage } from '../../messages' import { RevocationNotificationService } from '../RevocationNotificationService' -import { AnonCredsCredentialMetadata } from '../../../../../../../../anoncreds/src/utils/metadata' jest.mock('../../../../repository/CredentialRepository') const CredentialRepositoryMock = CredentialRepository as jest.Mock diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts index d8d6a42dcb..84d0a05779 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/V2CredentialProtocolOffer.test.ts @@ -1,11 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ import type { AgentContext } from '../../../../../agent' import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { - CredentialFormat, - CredentialFormatAcceptRequestOptions, - CredentialFormatCreateOfferOptions, - CredentialFormatService, -} from '../../../formats' +import type { CredentialFormat, CredentialFormatCreateOfferOptions, CredentialFormatService } from '../../../formats' import type { CreateCredentialOfferOptions } from '../../CredentialProtocolOptions' import { Subject } from 'rxjs' diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index 7e167fb4d0..6cb9a28e42 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -84,55 +84,56 @@ const indyCredentialFormat = new LegacyIndyCredentialFormatService() const jsonLdCredentialFormat = new JsonLdCredentialFormatService() const indyProofFormat = new LegacyIndyProofFormatService() -const indyJsonLdModules = { - credentials: new CredentialsModule({ - credentialProtocols: [ - new V1CredentialProtocol({ indyCredentialFormat }), - new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormat, jsonLdCredentialFormat], - }), - ], - }), - proofs: new ProofsModule({ - proofProtocols: [ - new V1ProofProtocol({ indyProofFormat }), - new V2ProofProtocol({ - proofFormats: [indyProofFormat], - }), - ], - }), - anoncreds: new AnonCredsModule({ - registries: [new IndySdkAnonCredsRegistry()], - }), - dids: new DidsModule({ - resolvers: [new IndySdkSovDidResolver(), new KeyDidResolver()], - registrars: [new IndySdkSovDidRegistrar(), new KeyDidRegistrar()], - }), - indySdk: new IndySdkModule({ - indySdk, - networks: [ - { - isProduction: false, - genesisPath, - id: randomUUID(), - indyNamespace: `pool:localtest`, - transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, - }, - ], - }), - cache: new CacheModule({ - cache: new InMemoryLruCache({ limit: 100 }), - }), - w3cVc: new W3cVcModule({ - documentLoader: customDocumentLoader, - }), -} as const +const getIndyJsonLdModules = () => + ({ + credentials: new CredentialsModule({ + credentialProtocols: [ + new V1CredentialProtocol({ indyCredentialFormat }), + new V2CredentialProtocol({ + credentialFormats: [indyCredentialFormat, jsonLdCredentialFormat], + }), + ], + }), + proofs: new ProofsModule({ + proofProtocols: [ + new V1ProofProtocol({ indyProofFormat }), + new V2ProofProtocol({ + proofFormats: [indyProofFormat], + }), + ], + }), + anoncreds: new AnonCredsModule({ + registries: [new IndySdkAnonCredsRegistry()], + }), + dids: new DidsModule({ + resolvers: [new IndySdkSovDidResolver(), new KeyDidResolver()], + registrars: [new IndySdkSovDidRegistrar(), new KeyDidRegistrar()], + }), + indySdk: new IndySdkModule({ + indySdk, + networks: [ + { + isProduction: false, + genesisPath, + id: randomUUID(), + indyNamespace: `pool:localtest`, + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, + }, + ], + }), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), + w3cVc: new W3cVcModule({ + documentLoader: customDocumentLoader, + }), + } as const) // TODO: extract these very specific tests to the jsonld format describe('V2 Credentials - JSON-LD - Ed25519', () => { - let faberAgent: Agent + let faberAgent: Agent> let faberReplay: EventReplaySubject - let aliceAgent: Agent + let aliceAgent: Agent> let aliceReplay: EventReplaySubject let aliceConnectionId: string let credentialDefinitionId: string @@ -144,7 +145,7 @@ describe('V2 Credentials - JSON-LD - Ed25519', () => { { endpoints: ['rxjs:faber'], }, - indyJsonLdModules + getIndyJsonLdModules() ) ) aliceAgent = new Agent( @@ -153,7 +154,7 @@ describe('V2 Credentials - JSON-LD - Ed25519', () => { { endpoints: ['rxjs:alice'], }, - indyJsonLdModules + getIndyJsonLdModules() ) ) diff --git a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts index a95ff845c7..68fb1a0103 100644 --- a/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v1-discover-features.e2e.test.ts @@ -14,14 +14,12 @@ import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' -const modules = getIndySdkModules() - const faberAgentOptions = getAgentOptions( 'Faber Discover Features V1 E2E', { endpoints: ['rxjs:faber'], }, - modules + getIndySdkModules() ) const aliceAgentOptions = getAgentOptions( @@ -29,7 +27,7 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - modules + getIndySdkModules() ) describe('v1 discover features', () => { diff --git a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts index e9649cdec1..f5a4b9f782 100644 --- a/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts +++ b/packages/core/src/modules/discover-features/__tests__/v2-discover-features.e2e.test.ts @@ -15,14 +15,12 @@ import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' -const modules = getIndySdkModules() - const faberAgentOptions = getAgentOptions( 'Faber Discover Features V2 E2E', { endpoints: ['rxjs:faber'], }, - modules + getIndySdkModules() ) const aliceAgentOptions = getAgentOptions( @@ -30,7 +28,7 @@ const aliceAgentOptions = getAgentOptions( { endpoints: ['rxjs:alice'], }, - modules + getIndySdkModules() ) describe('v2 discover features', () => { diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts index 34b37e21c6..0482ed4a86 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -252,17 +252,15 @@ describe('V2 Connectionless Proofs - Indy', () => { const unique = uuid().substring(0, 4) - const anonCredsModules = getLegacyAnonCredsModules({ - autoAcceptProofs: AutoAcceptProof.Always, - }) - const mediatorOptions = getAgentOptions( `Connectionless proofs with mediator Mediator-${unique}`, { autoAcceptMediationRequests: true, endpoints: ['rxjs:mediator'], }, - anonCredsModules + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) ) const mediatorMessages = new Subject() @@ -292,7 +290,9 @@ describe('V2 Connectionless Proofs - Indy', () => { }), mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - anonCredsModules + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) ) const aliceOptions = getAgentOptions( @@ -303,7 +303,9 @@ describe('V2 Connectionless Proofs - Indy', () => { }), mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - anonCredsModules + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) ) const faberAgent = new Agent(faberOptions) diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index b7cf2f9625..975c07e467 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -14,16 +14,14 @@ import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' -const modules = getIndySdkModules() - -const recipientAgentOptions = getAgentOptions('Mediation: Recipient', {}, modules) +const recipientAgentOptions = getAgentOptions('Mediation: Recipient', {}, getIndySdkModules()) const mediatorAgentOptions = getAgentOptions( 'Mediation: Mediator', { autoAcceptMediationRequests: true, endpoints: ['rxjs:mediator'], }, - modules + getIndySdkModules() ) const senderAgentOptions = getAgentOptions( @@ -31,7 +29,7 @@ const senderAgentOptions = getAgentOptions( { endpoints: ['rxjs:sender'], }, - modules + getIndySdkModules() ) describe('mediator establishment', () => { diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts b/packages/core/src/modules/routing/__tests__/pickup.test.ts index 2e12eae8ff..c67bac9b89 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ b/packages/core/src/modules/routing/__tests__/pickup.test.ts @@ -11,13 +11,12 @@ import { Agent } from '../../../agent/Agent' import { HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' -const modules = getIndySdkModules() const recipientOptions = getAgentOptions( 'Mediation: Recipient Pickup', { autoAcceptConnections: true, }, - modules + getIndySdkModules() ) const mediatorOptions = getAgentOptions( 'Mediation: Mediator Pickup', @@ -25,7 +24,7 @@ const mediatorOptions = getAgentOptions( autoAcceptConnections: true, endpoints: ['wss://mediator'], }, - modules + getIndySdkModules() ) describe('E2E Pick Up protocol', () => { diff --git a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap index 8fe31caafb..a64116a5f5 100644 --- a/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap +++ b/packages/core/src/storage/migration/__tests__/__snapshots__/0.1.test.ts.snap @@ -380,8 +380,6 @@ Object { "tags": Object { "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", "credentialIds": Array [], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, @@ -429,8 +427,6 @@ Object { "credentialIds": Array [ "a77114e1-c812-4bff-a53c-3d5003fcc278", ], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, @@ -679,8 +675,6 @@ Object { "tags": Object { "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", "credentialIds": Array [], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, @@ -728,8 +722,6 @@ Object { "credentialIds": Array [ "19c1f29f-d2df-486c-b8c6-950c403fa7d9", ], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, @@ -2823,8 +2815,6 @@ Object { "tags": Object { "connectionId": "0b6de73d-b376-430f-b2b4-f6e51407bb66", "credentialIds": Array [], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, @@ -2872,8 +2862,6 @@ Object { "credentialIds": Array [ "a77114e1-c812-4bff-a53c-3d5003fcc278", ], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "578e73da-c3be-43d4-949b-7aadfd5a6eae", }, @@ -3122,8 +3110,6 @@ Object { "tags": Object { "connectionId": "cd66cbf1-5721-449e-8724-f4d8dcef1bc4", "credentialIds": Array [], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, @@ -3171,8 +3157,6 @@ Object { "credentialIds": Array [ "19c1f29f-d2df-486c-b8c6-950c403fa7d9", ], - "indyCredentialRevocationId": undefined, - "indyRevocationRegistryId": undefined, "state": "done", "threadId": "e2c2194c-6ac6-4b27-9030-18887c79b5eb", }, diff --git a/packages/core/tests/agents.test.ts b/packages/core/tests/agents.test.ts index 5a0f9331ce..9bded8ba18 100644 --- a/packages/core/tests/agents.test.ts +++ b/packages/core/tests/agents.test.ts @@ -8,21 +8,19 @@ import { HandshakeProtocol } from '../src/modules/connections' import { waitForBasicMessage, getAgentOptions } from './helpers' import { setupSubjectTransports } from './transport' -const modules = getIndySdkModules() - const aliceAgentOptions = getAgentOptions( 'Agents Alice', { endpoints: ['rxjs:alice'], }, - modules + getIndySdkModules() ) const bobAgentOptions = getAgentOptions( 'Agents Bob', { endpoints: ['rxjs:bob'], }, - modules + getIndySdkModules() ) describe('agents', () => { diff --git a/packages/core/tests/connections.test.ts b/packages/core/tests/connections.test.ts index 444375e573..2525879598 100644 --- a/packages/core/tests/connections.test.ts +++ b/packages/core/tests/connections.test.ts @@ -26,27 +26,26 @@ describe('connections', () => { let mediatorAgent: Agent beforeEach(async () => { - const modules = getIndySdkModules() const faberAgentOptions = getAgentOptions( 'Faber Agent Connections', { endpoints: ['rxjs:faber'], }, - modules + getIndySdkModules() ) const aliceAgentOptions = getAgentOptions( 'Alice Agent Connections', { endpoints: ['rxjs:alice'], }, - modules + getIndySdkModules() ) const acmeAgentOptions = getAgentOptions( 'Acme Agent Connections', { endpoints: ['rxjs:acme'], }, - modules + getIndySdkModules() ) const mediatorAgentOptions = getAgentOptions( 'Mediator Agent Connections', @@ -54,7 +53,7 @@ describe('connections', () => { endpoints: ['rxjs:mediator'], autoAcceptMediationRequests: true, }, - modules + getIndySdkModules() ) faberAgent = new Agent(faberAgentOptions) diff --git a/packages/core/tests/indySdk.ts b/packages/core/tests/indySdk.ts index f1cc48315a..b5e5a3075d 100644 --- a/packages/core/tests/indySdk.ts +++ b/packages/core/tests/indySdk.ts @@ -1,3 +1,3 @@ -import indySdk from 'indy-sdk' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' export { indySdk } diff --git a/packages/core/tests/multi-protocol-version.test.ts b/packages/core/tests/multi-protocol-version.test.ts index 454b352f5d..4f7596d5ff 100644 --- a/packages/core/tests/multi-protocol-version.test.ts +++ b/packages/core/tests/multi-protocol-version.test.ts @@ -11,20 +11,19 @@ import { OutboundMessageContext } from '../src/agent/models' import { getAgentOptions } from './helpers' import { setupSubjectTransports } from './transport' -const modules = getIndySdkModules() const aliceAgentOptions = getAgentOptions( 'Multi Protocol Versions - Alice', { endpoints: ['rxjs:alice'], }, - modules + getIndySdkModules() ) const bobAgentOptions = getAgentOptions( 'Multi Protocol Versions - Bob', { endpoints: ['rxjs:bob'], }, - modules + getIndySdkModules() ) describe('multi version protocols', () => { diff --git a/packages/core/tests/oob-mediation-provision.test.ts b/packages/core/tests/oob-mediation-provision.test.ts index 011a64be7a..6468d27d7f 100644 --- a/packages/core/tests/oob-mediation-provision.test.ts +++ b/packages/core/tests/oob-mediation-provision.test.ts @@ -9,14 +9,12 @@ import { MediationState, MediatorPickupStrategy } from '../src/modules/routing' import { getAgentOptions, waitForBasicMessage } from './helpers' import { setupSubjectTransports } from './transport' -const modules = getIndySdkModules() - const faberAgentOptions = getAgentOptions( 'OOB mediation provision - Faber Agent', { endpoints: ['rxjs:faber'], }, - modules + getIndySdkModules() ) const aliceAgentOptions = getAgentOptions( 'OOB mediation provision - Alice Recipient Agent', @@ -24,7 +22,7 @@ const aliceAgentOptions = getAgentOptions( endpoints: ['rxjs:alice'], mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - modules + getIndySdkModules() ) const mediatorAgentOptions = getAgentOptions( 'OOB mediation provision - Mediator Agent', @@ -32,7 +30,7 @@ const mediatorAgentOptions = getAgentOptions( endpoints: ['rxjs:mediator'], autoAcceptMediationRequests: true, }, - modules + getIndySdkModules() ) describe('out of band with mediation set up with provision method', () => { diff --git a/packages/core/tests/oob-mediation.test.ts b/packages/core/tests/oob-mediation.test.ts index 33388a7a24..72d92ea0de 100644 --- a/packages/core/tests/oob-mediation.test.ts +++ b/packages/core/tests/oob-mediation.test.ts @@ -22,14 +22,12 @@ import { import { getAgentOptions, waitForBasicMessage } from './helpers' -const modules = getIndySdkModules() - const faberAgentOptions = getAgentOptions( 'OOB mediation - Faber Agent', { endpoints: ['rxjs:faber'], }, - modules + getIndySdkModules() ) const aliceAgentOptions = getAgentOptions( 'OOB mediation - Alice Recipient Agent', @@ -39,7 +37,7 @@ const aliceAgentOptions = getAgentOptions( // we should return that we only support the mediator role so we don't have to explicitly declare this mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - modules + getIndySdkModules() ) const mediatorAgentOptions = getAgentOptions( 'OOB mediation - Mediator Agent', @@ -47,7 +45,7 @@ const mediatorAgentOptions = getAgentOptions( endpoints: ['rxjs:mediator'], autoAcceptMediationRequests: true, }, - modules + getIndySdkModules() ) describe('out of band with mediation', () => { diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 46b05f80c4..836ca766bd 100644 --- a/packages/core/tests/oob.test.ts +++ b/packages/core/tests/oob.test.ts @@ -26,21 +26,23 @@ import { JsonEncoder } from '../src/utils' import { TestMessage } from './TestMessage' import { getAgentOptions, waitForCredentialRecord } from './helpers' -const legacyAnonCredsModules = getLegacyAnonCredsModules() - const faberAgentOptions = getAgentOptions( 'Faber Agent OOB', { endpoints: ['rxjs:faber'], }, - legacyAnonCredsModules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) const aliceAgentOptions = getAgentOptions( 'Alice Agent OOB', { endpoints: ['rxjs:alice'], }, - legacyAnonCredsModules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) describe('out of band', () => { @@ -62,8 +64,8 @@ describe('out of band', () => { autoAcceptConnection: false, } - let faberAgent: Agent - let aliceAgent: Agent + let faberAgent: Agent> + let aliceAgent: Agent> let credentialTemplate: CreateCredentialOfferOptions<[V1CredentialProtocol]> beforeAll(async () => { diff --git a/packages/core/tests/wallet.test.ts b/packages/core/tests/wallet.test.ts index 13856dd8de..2168ce72ac 100644 --- a/packages/core/tests/wallet.test.ts +++ b/packages/core/tests/wallet.test.ts @@ -12,10 +12,8 @@ import { WalletNotFoundError } from '../src/wallet/error/WalletNotFoundError' import { getAgentOptions } from './helpers' -const modules = getIndySdkModules() - -const aliceAgentOptions = getAgentOptions('wallet-tests-Alice', {}, modules) -const bobAgentOptions = getAgentOptions('wallet-tests-Bob', {}, modules) +const aliceAgentOptions = getAgentOptions('wallet-tests-Alice', {}, getIndySdkModules()) +const bobAgentOptions = getAgentOptions('wallet-tests-Bob', {}, getIndySdkModules()) describe('wallet', () => { let aliceAgent: Agent diff --git a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts index 3d99e89062..a0548c0223 100644 --- a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts @@ -4,7 +4,7 @@ import { agentDependencies, getAgentConfig } from '../../core/tests/helpers' import { IndySdkModule } from '../src' import { IndySdkAnonCredsRegistry } from '../src/anoncreds/services/IndySdkAnonCredsRegistry' -import { indySdkModuleConfig } from './setupIndySdkModule' +import { getIndySdkModuleConfig } from './setupIndySdkModule' const agentConfig = getAgentConfig('IndySdkAnonCredsRegistry') @@ -12,7 +12,7 @@ const agent = new Agent({ config: agentConfig, dependencies: agentDependencies, modules: { - indySdk: new IndySdkModule(indySdkModuleConfig), + indySdk: new IndySdkModule(getIndySdkModuleConfig()), }, }) diff --git a/packages/indy-sdk/tests/setupIndySdkModule.ts b/packages/indy-sdk/tests/setupIndySdkModule.ts index 7283d6bb92..c0e8ee1313 100644 --- a/packages/indy-sdk/tests/setupIndySdkModule.ts +++ b/packages/indy-sdk/tests/setupIndySdkModule.ts @@ -6,21 +6,22 @@ import { IndySdkModule, IndySdkModuleConfig, IndySdkSovDidRegistrar, IndySdkSovD export { indySdk } -export const indySdkModuleConfig = new IndySdkModuleConfig({ - indySdk, - networks: [ - { - id: `localhost-${utils.uuid()}`, - isProduction: false, - genesisPath, - indyNamespace: 'pool:localtest', - transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, - }, - ], -}) +export const getIndySdkModuleConfig = () => + new IndySdkModuleConfig({ + indySdk, + networks: [ + { + id: `localhost-${utils.uuid()}`, + isProduction: false, + genesisPath, + indyNamespace: 'pool:localtest', + transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, + }, + ], + }) export const getIndySdkModules = () => ({ - indySdk: new IndySdkModule(indySdkModuleConfig), + indySdk: new IndySdkModule(getIndySdkModuleConfig()), dids: new DidsModule({ registrars: [new IndySdkSovDidRegistrar(), new KeyDidRegistrar()], resolvers: [new IndySdkSovDidResolver(), new KeyDidResolver()], diff --git a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts index 99639dbb8a..dc09c9cbb7 100644 --- a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts @@ -53,10 +53,10 @@ describe('dids', () => { expect(JsonTransformer.toJSON(did)).toMatchObject({ didDocumentMetadata: { - qualifiedIndyDid: `did:indy:localhost:${indyDid}`, + qualifiedIndyDid: `did:indy:pool:localtest:${indyDid}`, }, didRegistrationMetadata: { - didIndyNamespace: 'localhost', + didIndyNamespace: 'pool:localtest', }, didState: { state: 'finished', diff --git a/tests/e2e-http.test.ts b/tests/e2e-http.test.ts index 69c4c8abf2..04bc30ea17 100644 --- a/tests/e2e-http.test.ts +++ b/tests/e2e-http.test.ts @@ -8,16 +8,14 @@ import { e2eTest } from './e2e-test' import { HttpOutboundTransport, Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { HttpInboundTransport } from '@aries-framework/node' -const modules = getLegacyAnonCredsModules({ - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, -}) - const recipientAgentOptions = getAgentOptions( 'E2E HTTP Recipient', { mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) const mediatorPort = 3000 @@ -27,7 +25,9 @@ const mediatorAgentOptions = getAgentOptions( endpoints: [`http://localhost:${mediatorPort}`], autoAcceptMediationRequests: true, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) const senderPort = 3001 @@ -38,7 +38,9 @@ const senderAgentOptions = getAgentOptions( mediatorPollingInterval: 1000, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) describe('E2E HTTP tests', () => { diff --git a/tests/e2e-subject.test.ts b/tests/e2e-subject.test.ts index 8d8ae17eca..6ec26ef417 100644 --- a/tests/e2e-subject.test.ts +++ b/tests/e2e-subject.test.ts @@ -13,16 +13,14 @@ import { Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-fram import { SubjectInboundTransport } from './transport/SubjectInboundTransport' import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' -const modules = getLegacyAnonCredsModules({ - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, -}) - const recipientAgentOptions = getAgentOptions( 'E2E Subject Recipient', { mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) const mediatorAgentOptions = getAgentOptions( 'E2E Subject Mediator', @@ -30,7 +28,9 @@ const mediatorAgentOptions = getAgentOptions( endpoints: ['rxjs:mediator'], autoAcceptMediationRequests: true, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) const senderAgentOptions = getAgentOptions( 'E2E Subject Sender', @@ -39,7 +39,9 @@ const senderAgentOptions = getAgentOptions( mediatorPollingInterval: 1000, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) describe('E2E Subject tests', () => { diff --git a/tests/e2e-ws-pickup-v2.test.ts b/tests/e2e-ws-pickup-v2.test.ts index 0fcd01b5a7..16ace3cd90 100644 --- a/tests/e2e-ws-pickup-v2.test.ts +++ b/tests/e2e-ws-pickup-v2.test.ts @@ -3,21 +3,20 @@ import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnon import { getLegacyAnonCredsModules } from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { getAgentOptions } from '../packages/core/tests/helpers' +import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' + import { e2eTest } from './e2e-test' -import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -const modules = getLegacyAnonCredsModules({ - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, -}) - const recipientOptions = getAgentOptions( 'E2E WS Pickup V2 Recipient ', { mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) // FIXME: port numbers should not depend on availability from other test suites that use web sockets @@ -28,7 +27,9 @@ const mediatorOptions = getAgentOptions( endpoints: [`ws://localhost:${mediatorPort}`], autoAcceptMediationRequests: true, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) const senderPort = 4101 @@ -40,7 +41,9 @@ const senderOptions = getAgentOptions( mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) describe('E2E WS Pickup V2 tests', () => { diff --git a/tests/e2e-ws.test.ts b/tests/e2e-ws.test.ts index d7e1f76cdd..942ea92971 100644 --- a/tests/e2e-ws.test.ts +++ b/tests/e2e-ws.test.ts @@ -8,16 +8,14 @@ import { e2eTest } from './e2e-test' import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -const modules = getLegacyAnonCredsModules({ - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, -}) - const recipientAgentOptions = getAgentOptions( 'E2E WS Recipient ', { mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) const mediatorPort = 4000 @@ -27,7 +25,9 @@ const mediatorAgentOptions = getAgentOptions( endpoints: [`ws://localhost:${mediatorPort}`], autoAcceptMediationRequests: true, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) const senderPort = 4001 @@ -38,7 +38,9 @@ const senderAgentOptions = getAgentOptions( mediatorPollingInterval: 1000, mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, }, - modules + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) ) describe('E2E WS tests', () => { From e7b00f5355f181f8aa4cc6457993b558f920dd0a Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sat, 18 Feb 2023 01:27:08 +0100 Subject: [PATCH 19/22] refactor!: remove master secret id from wallet Signed-off-by: Timo Glastra --- .../src/services/AnonCredsRsHolderService.ts | 4 +- .../legacy-indy-format-services.test.ts | 20 +++++- .../v1-connectionless-proofs.e2e.test.ts | 5 ++ .../anoncreds/tests/legacyAnonCredsSetup.ts | 6 ++ packages/askar/src/wallet/AskarWallet.ts | 12 ---- .../src/wallet/__tests__/AskarWallet.test.ts | 8 --- .../v2-connectionless-credentials.e2e.test.ts | 6 ++ ...f.credentials.propose-offerED25519.test.ts | 6 ++ .../v2-indy-connectionless-proofs.e2e.test.ts | 5 ++ packages/core/src/types.ts | 1 - .../services/IndySdkHolderService.ts | 33 ++++++++-- packages/indy-sdk/src/wallet/IndySdkWallet.ts | 62 ------------------- .../wallet/__tests__/IndySdkWallet.test.ts | 33 ---------- tests/e2e-test.ts | 6 ++ 14 files changed, 84 insertions(+), 123 deletions(-) diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index 4a34024851..b4c1e02f53 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -199,7 +199,9 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { if (!linkSecretRecord) { // No default link secret - throw new AnonCredsRsError('No default link secret has been found') + throw new AnonCredsRsError( + 'No link secret provided to createCredentialRequest and no default link secret has been found' + ) } const { credentialRequest, credentialRequestMetadata } = CredentialRequest.create({ diff --git a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts index 33a306617a..850e9c9a0f 100644 --- a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts +++ b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts @@ -6,13 +6,16 @@ import { CredentialPreviewAttribute, ProofExchangeRecord, ProofState, + EventEmitter, } from '@aries-framework/core' import * as indySdk from 'indy-sdk' +import { Subject } from 'rxjs' -import { getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' import { IndySdkHolderService, IndySdkIssuerService, + IndySdkStorageService, IndySdkVerifierService, IndySdkWallet, } from '../../../../indy-sdk/src' @@ -20,6 +23,7 @@ import { IndySdkRevocationService } from '../../../../indy-sdk/src/anoncreds/ser import { indyDidFromPublicKeyBase58 } from '../../../../indy-sdk/src/utils/did' import { InMemoryAnonCredsRegistry } from '../../../tests/InMemoryAnonCredsRegistry' import { AnonCredsModuleConfig } from '../../AnonCredsModuleConfig' +import { AnonCredsLinkSecretRecord, AnonCredsLinkSecretRepository } from '../../repository' import { AnonCredsHolderServiceSymbol, AnonCredsIssuerServiceSymbol, @@ -40,6 +44,9 @@ const anonCredsVerifierService = new IndySdkVerifierService(indySdk) const anonCredsHolderService = new IndySdkHolderService(anonCredsRevocationService, indySdk) const anonCredsIssuerService = new IndySdkIssuerService(indySdk) const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) +const storageService = new IndySdkStorageService(indySdk) +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const anonCredsLinkSecretRepository = new AnonCredsLinkSecretRepository(storageService, eventEmitter) const agentContext = getAgentContext({ registerInstances: [ [AnonCredsIssuerServiceSymbol, anonCredsIssuerService], @@ -47,6 +54,7 @@ const agentContext = getAgentContext({ [AnonCredsVerifierServiceSymbol, anonCredsVerifierService], [AnonCredsRegistryService, new AnonCredsRegistryService()], [AnonCredsModuleConfig, anonCredsModuleConfig], + [AnonCredsLinkSecretRepository, anonCredsLinkSecretRepository], ], agentConfig, wallet, @@ -71,6 +79,16 @@ describe('Legacy indy format services', () => { const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) const indyDid = indyDidFromPublicKeyBase58(key.publicKeyBase58) + // Create link secret + await anonCredsHolderService.createLinkSecret(agentContext, { + linkSecretId: 'link-secret-id', + }) + const anonCredsLinkSecret = new AnonCredsLinkSecretRecord({ + linkSecretId: 'link-secret-id', + }) + anonCredsLinkSecret.setTag('isDefault', true) + await anonCredsLinkSecretRepository.save(agentContext, anonCredsLinkSecret) + const schema = await anonCredsIssuerService.createSchema(agentContext, { attrNames: ['name', 'age'], issuerId: indyDid, diff --git a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts index df66a6217d..be1b7c1deb 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -332,6 +332,11 @@ describe('V1 Proofs - Connectionless - Indy', () => { expect(faberConnection.isReady).toBe(true) expect(aliceConnection.isReady).toBe(true) + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + await issueLegacyAnonCredsCredential({ issuerAgent: faberAgent, issuerReplay: faberReplay, diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index e5a1082b64..9b6da7fd32 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -329,6 +329,12 @@ export async function setupAnonCredsTests< await holderAgent.initialize() if (verifierAgent) await verifierAgent.initialize() + // Create default link secret for holder + await holderAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + const { credentialDefinition, schema } = await prepareForAnonCredsIssuance(issuerAgent, { attributeNames, // TODO: replace with more dynamic / generic value We should create a did using the dids module diff --git a/packages/askar/src/wallet/AskarWallet.ts b/packages/askar/src/wallet/AskarWallet.ts index af94f14eea..5a210ce925 100644 --- a/packages/askar/src/wallet/AskarWallet.ts +++ b/packages/askar/src/wallet/AskarWallet.ts @@ -110,16 +110,6 @@ export class AskarWallet implements Wallet { return this._session } - public get masterSecretId() { - if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { - throw new AriesFrameworkError( - 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' - ) - } - - return this.walletConfig?.masterSecretId ?? this.walletConfig.id - } - /** * Dispose method is called when an agent context is disposed. */ @@ -156,8 +146,6 @@ export class AskarWallet implements Wallet { }) this.walletConfig = walletConfig this._session = await this._store.openSession() - - // TODO: Master Secret creation (now part of IndyCredx/AnonCreds) } catch (error) { // FIXME: Askar should throw a Duplicate error code, but is currently returning Encryption // And if we provide the very same wallet key, it will open it without any error diff --git a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts index 94732a15b0..8385e6fa34 100644 --- a/packages/askar/src/wallet/__tests__/AskarWallet.test.ts +++ b/packages/askar/src/wallet/__tests__/AskarWallet.test.ts @@ -48,10 +48,6 @@ describe('AskarWallet basic operations', () => { await askarWallet.delete() }) - test('Get the Master Secret', () => { - expect(askarWallet.masterSecretId).toEqual('Wallet: AskarWalletTest') - }) - test('Get the wallet store', () => { expect(askarWallet.store).toEqual(expect.any(Store)) }) @@ -99,10 +95,6 @@ describe('AskarWallet basic operations', () => { }) await expect(askarWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) }) - - test('masterSecretId is equal to wallet ID by default', async () => { - expect(askarWallet.masterSecretId).toEqual(walletConfig.id) - }) }) describe.skip('Currently, all KeyTypes are supported by Askar natively', () => { diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts index 95c48a3ee9..cb2fd2b0c1 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-connectionless-credentials.e2e.test.ts @@ -66,6 +66,12 @@ describe('V2 Connectionless Credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() + // Create link secret for alice + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { issuerId: faberAgent.publicDid?.did as string, attributeNames: ['name', 'age'], diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index 6cb9a28e42..dae78574c7 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -167,6 +167,12 @@ describe('V2 Credentials - JSON-LD - Ed25519', () => { await aliceAgent.initialize() ;[, { id: aliceConnectionId }] = await makeConnection(faberAgent, aliceAgent) + // Create link secret for alice + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { attributeNames: ['name', 'age', 'profile_picture', 'x-ray'], issuerId: faberAgent.publicDid?.did as string, diff --git a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts index 0482ed4a86..5b343f3471 100644 --- a/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts +++ b/packages/core/src/modules/proofs/protocol/v2/__tests__/v2-indy-connectionless-proofs.e2e.test.ts @@ -322,6 +322,11 @@ describe('V2 Connectionless Proofs - Indy', () => { ) agents = [aliceAgent, faberAgent, mediatorAgent] + await aliceAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + const { credentialDefinition } = await prepareForAnonCredsIssuance(faberAgent, { attributeNames: ['name', 'age', 'image_0', 'image_1'], issuerId: faberAgent.publicDid?.did as string, diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index f621d4393e..4cd29a4ba2 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -22,7 +22,6 @@ export interface WalletConfig { key: string keyDerivationMethod?: KeyDerivationMethod storage?: WalletStorageConfig - masterSecretId?: string } export interface WalletConfigRekey { diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index d5e82deea7..394ed39198 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -25,7 +25,8 @@ import type { IndyProofRequest, } from 'indy-sdk' -import { injectable, inject, utils } from '@aries-framework/core' +import { AnonCredsLinkSecretRepository } from '@aries-framework/anoncreds' +import { AriesFrameworkError, injectable, inject, utils } from '@aries-framework/core' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' @@ -80,6 +81,8 @@ export class IndySdkHolderService implements AnonCredsHolderService { assertIndySdkWallet(agentContext.wallet) + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + try { agentContext.config.logger.debug('Creating Indy Proof') const indyRevocationStates: RevStates = await this.indyRevocationService.createRevocationState( @@ -114,11 +117,19 @@ export class IndySdkHolderService implements AnonCredsHolderService { indySchemas[schemaId] = indySdkSchemaFromAnonCreds(schemaId, schema, seqNoMap[schemaId]) } + const linkSecretRecord = await linkSecretRepository.findDefault(agentContext) + if (!linkSecretRecord) { + // No default link secret + throw new AriesFrameworkError( + 'No default link secret found. Indy SDK requires a default link secret to be created before creating a proof.' + ) + } + const indyProof = await this.indySdk.proverCreateProof( agentContext.wallet.handle, proofRequest as IndyProofRequest, this.parseSelectedCredentials(selectedCredentials), - agentContext.wallet.masterSecretId, + linkSecretRecord.linkSecretId, indySchemas, indyCredentialDefinitions, indyRevocationStates @@ -201,10 +212,24 @@ export class IndySdkHolderService implements AnonCredsHolderService { ): Promise { assertIndySdkWallet(agentContext.wallet) + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + // We just generate a prover did like string, as it's not used for anything and we don't need // to prove ownership of the did. It's deprecated in AnonCreds v1, but kept for backwards compatibility const proverDid = generateLegacyProverDidLikeString() + // If a link secret is specified, use it. Otherwise, attempt to use default link secret + const linkSecretRecord = options.linkSecretId + ? await linkSecretRepository.getByLinkSecretId(agentContext, options.linkSecretId) + : await linkSecretRepository.findDefault(agentContext) + + if (!linkSecretRecord) { + // No default link secret + throw new AriesFrameworkError( + 'No link secret provided to createCredentialRequest and no default link secret has been found' + ) + } + try { const result = await this.indySdk.proverCreateCredentialReq( agentContext.wallet.handle, @@ -213,9 +238,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { // NOTE: Is it safe to use the cred_def_id from the offer? I think so. You can't create a request // for a cred def that is not in the offer indySdkCredentialDefinitionFromAnonCreds(options.credentialOffer.cred_def_id, options.credentialDefinition), - // FIXME: we need to remove the masterSecret from the wallet, as it is AnonCreds specific - // Issue: https://github.com/hyperledger/aries-framework-javascript/issues/1198 - agentContext.wallet.masterSecretId + linkSecretRecord.linkSecretId ) return { diff --git a/packages/indy-sdk/src/wallet/IndySdkWallet.ts b/packages/indy-sdk/src/wallet/IndySdkWallet.ts index f5c5b6257d..2961ac8d0b 100644 --- a/packages/indy-sdk/src/wallet/IndySdkWallet.ts +++ b/packages/indy-sdk/src/wallet/IndySdkWallet.ts @@ -81,16 +81,6 @@ export class IndySdkWallet implements Wallet { return this.walletHandle } - public get masterSecretId() { - if (!this.isInitialized || !(this.walletConfig?.id || this.walletConfig?.masterSecretId)) { - throw new AriesFrameworkError( - 'Wallet has not been initialized yet. Make sure to await agent.initialize() before using the agent.' - ) - } - - return this.walletConfig?.masterSecretId ?? this.walletConfig.id - } - /** * Dispose method is called when an agent context is disposed. */ @@ -155,15 +145,8 @@ export class IndySdkWallet implements Wallet { await this.indySdk.createWallet(this.walletStorageConfig(walletConfig), this.walletCredentials(walletConfig)) this.walletConfig = walletConfig - // We usually want to create master secret only once, therefore, we can to do so when creating a wallet. await this.open(walletConfig) - - // We need to open wallet before creating master secret because we need wallet handle here. - await this.createMasterSecret(this.handle, this.masterSecretId) } catch (error) { - // If an error ocurred while creating the master secret, we should close the wallet - if (this.isInitialized) await this.close() - if (isIndyError(error, 'WalletAlreadyExistsError')) { const errorMessage = `Wallet '${walletConfig.id}' already exists` this.logger.debug(errorMessage) @@ -394,51 +377,6 @@ export class IndySdkWallet implements Wallet { } } - /** - * Create master secret with specified id in currently opened wallet. - * - * If a master secret by this id already exists in the current wallet, the method - * will return without doing anything. - * - * @throws {WalletError} if an error occurs - */ - private async createMasterSecret(walletHandle: number, masterSecretId: string): Promise { - this.logger.debug(`Creating master secret with id '${masterSecretId}' in wallet with handle '${walletHandle}'`) - - try { - await this.indySdk.proverCreateMasterSecret(walletHandle, masterSecretId) - - return masterSecretId - } catch (error) { - if (isIndyError(error, 'AnoncredsMasterSecretDuplicateNameError')) { - // master secret id is the same as the master secret id passed in the create function - // so if it already exists we can just assign it. - this.logger.debug( - `Master secret with id '${masterSecretId}' already exists in wallet with handle '${walletHandle}'`, - { - indyError: 'AnoncredsMasterSecretDuplicateNameError', - } - ) - - return masterSecretId - } else { - if (!isIndyError(error)) { - throw new AriesFrameworkError('Attempted to throw Indy error, but it was not an Indy error') - } - - this.logger.error(`Error creating master secret with id ${masterSecretId}`, { - indyError: error.indyName, - error, - }) - - throw new WalletError( - `Error creating master secret with id ${masterSecretId} in wallet with handle '${walletHandle}'`, - { cause: error } - ) - } - } - } - public async initPublicDid(didConfig: DidConfig) { const { did, verkey } = await this.createDid(didConfig) this.publicDidInfo = { diff --git a/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts index 23a1aebdf8..b80a2b1b98 100644 --- a/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts +++ b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts @@ -20,14 +20,6 @@ const walletConfig: WalletConfig = { keyDerivationMethod: KeyDerivationMethod.Raw, } -const walletConfigWithMasterSecretId: WalletConfig = { - id: 'Wallet: WalletTestWithMasterSecretId', - // generated using indy.generateWalletKey - key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', - keyDerivationMethod: KeyDerivationMethod.Raw, - masterSecretId: 'customMasterSecretId', -} - describe('IndySdkWallet', () => { let indySdkWallet: IndySdkWallet @@ -51,10 +43,6 @@ describe('IndySdkWallet', () => { }) }) - test('Get the Master Secret', () => { - expect(indySdkWallet.masterSecretId).toEqual('Wallet: IndySdkWalletTest') - }) - test('Get the wallet handle', () => { expect(indySdkWallet.handle).toEqual(expect.any(Number)) }) @@ -101,25 +89,4 @@ describe('IndySdkWallet', () => { }) await expect(indySdkWallet.verify({ key: ed25519Key, data: message, signature })).resolves.toStrictEqual(true) }) - - test('masterSecretId is equal to wallet ID by default', async () => { - expect(indySdkWallet.masterSecretId).toEqual(walletConfig.id) - }) -}) - -describe('IndySdkWallet with custom Master Secret Id', () => { - let indySdkWallet: IndySdkWallet - - beforeEach(async () => { - indySdkWallet = new IndySdkWallet(indySdk, testLogger, new SigningProviderRegistry([])) - await indySdkWallet.createAndOpen(walletConfigWithMasterSecretId) - }) - - afterEach(async () => { - await indySdkWallet.delete() - }) - - test('masterSecretId is set by config', async () => { - expect(indySdkWallet.masterSecretId).toEqual(walletConfigWithMasterSecretId.masterSecretId) - }) }) diff --git a/tests/e2e-test.ts b/tests/e2e-test.ts index b1bee59014..4d281abcb9 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -50,6 +50,12 @@ export async function e2eTest({ const [recipientSenderConnection, senderRecipientConnection] = await makeConnection(recipientAgent, senderAgent) expect(recipientSenderConnection).toBeConnectedWith(senderRecipientConnection) + // Create default link secret + await recipientAgent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'default', + setAsDefault: true, + }) + // Issue credential from sender to recipient const { credentialDefinition } = await prepareForAnonCredsIssuance(senderAgent, { attributeNames: ['name', 'age', 'dateOfBirth'], From 3fb4d844b5e338c91aa122047ebb7434050f3a04 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sun, 19 Feb 2023 18:31:31 +0100 Subject: [PATCH 20/22] fixes after merge Signed-off-by: Timo Glastra --- demo/src/Alice.ts | 2 +- demo/src/BaseAgent.ts | 25 +++++++++---------- demo/src/Faber.ts | 2 +- ...proof.credentials.propose-offerBbs.test.ts | 2 +- ...ldproof.connectionless-credentials.test.ts | 2 +- ...v2.ldproof.credentials-auto-accept.test.ts | 4 +-- ...f.credentials.propose-offerED25519.test.ts | 2 +- .../routing/__tests__/mediation.test.ts | 3 +++ .../__tests__/IndySdkSovDidRegistrar.test.ts | 25 ++++++++++--------- .../tests/sov-did-registrar.e2e.test.ts | 2 +- 10 files changed, 36 insertions(+), 33 deletions(-) diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index 9c53abbbc0..2de378d8c1 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -8,7 +8,7 @@ export class Alice extends BaseAgent { public connectionRecordFaberId?: string public constructor(port: number, name: string) { - super({ port, name, useSharedComponents: false }) + super({ port, name, useLegacyIndySdk: true }) this.connected = false } diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index d5b568297a..26429ca358 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -12,6 +12,7 @@ import { import { AnonCredsRsModule } from '@aries-framework/anoncreds-rs' import { AskarModule } from '@aries-framework/askar' import { + TypedArrayEncoder, KeyType, DidsModule, V2ProofProtocol, @@ -45,7 +46,7 @@ const indyNetworkConfig = { connectOnStartup: true, } satisfies IndySdkPoolConfig | IndyVdrPoolConfig -type DemoAgent = Agent | ReturnType> +type DemoAgent = Agent | ReturnType> export class BaseAgent { public port: number @@ -53,16 +54,16 @@ export class BaseAgent { public config: InitConfig public agent: DemoAgent public anonCredsIssuerId: string - public usesSharedComponents: boolean + public useLegacyIndySdk: boolean public constructor({ port, name, - useSharedComponents, + useLegacyIndySdk = false, }: { port: number name: string - useSharedComponents: boolean + useLegacyIndySdk?: boolean }) { this.name = name this.port = port @@ -82,12 +83,12 @@ export class BaseAgent { // TODO: do not hardcode this this.anonCredsIssuerId = '2jEvRuKmfBJTRa7QowDpNN' - this.usesSharedComponents = useSharedComponents + this.useLegacyIndySdk = useLegacyIndySdk this.agent = new Agent({ config, dependencies: agentDependencies, - modules: useSharedComponents ? getSharedComponentModules() : getIndySdkModules(), + modules: useLegacyIndySdk ? getLegacyIndySdkModules() : getAskarAnonCredsIndyModules(), }) this.agent.registerInboundTransport(new HttpInboundTransport({ port })) this.agent.registerOutboundTransport(new HttpOutboundTransport()) @@ -100,14 +101,12 @@ export class BaseAgent { // We need to make sure the key to submit transactions is created. We should update this to use the dids module, and allow // to add an existing did based on a seed/secretKey, and not register it on the the ledger. However for Indy SDK we currently // use the deprecated publicDidSeed property (which will register the did in the wallet), and for Askar we manually create the key - // in the wallet. Additional issue is that when creating a key in askar using the seed, it will give different results than indy-sdk - // but if we create the key in askar from the seed, but use it as the secret key, it will work. - // For IndySDK we can't call createKey as it won't allow to sign the transactions in that case, as it needs to be a did - if (this.usesSharedComponents) { + // in the wallet. + if (!this.useLegacyIndySdk) { try { await this.agent.context.wallet.createKey({ keyType: KeyType.Ed25519, - seed: 'afjdemoverysercure00000000000000', + privateKey: TypedArrayEncoder.fromString('afjdemoverysercure00000000000000'), }) } catch (error) { // We assume the key already exists, and that's why askar failed @@ -118,7 +117,7 @@ export class BaseAgent { } } -function getSharedComponentModules() { +function getAskarAnonCredsIndyModules() { const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService() const legacyIndyProofFormatService = new LegacyIndyProofFormatService() @@ -159,7 +158,7 @@ function getSharedComponentModules() { } as const } -function getIndySdkModules() { +function getLegacyIndySdkModules() { const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService() const legacyIndyProofFormatService = new LegacyIndyProofFormatService() diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index 655714d7d1..4585f82c7d 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -14,7 +14,7 @@ export class Faber extends BaseAgent { public ui: BottomBar public constructor(port: number, name: string) { - super({ port, name, useSharedComponents: true }) + super({ port, name }) this.ui = new ui.BottomBar() } diff --git a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts index ce2a9b5010..2205fde9b0 100644 --- a/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts +++ b/packages/bbs-signatures/tests/v2.ldproof.credentials.propose-offerBbs.test.ts @@ -67,7 +67,7 @@ describeSkipNode17And18('credentials, BBS+ signature', () => { await faberAgent.context.wallet.createKey({ keyType: KeyType.Ed25519, - seed: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), }) await faberAgent.context.wallet.createKey({ keyType: KeyType.Bls12381g2, diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts index f90e330d02..7472cca215 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.connectionless-credentials.test.ts @@ -47,7 +47,7 @@ describe('credentials', () => { })) await faberAgent.context.wallet.createKey({ - seed: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), keyType: KeyType.Ed25519, }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts index ee25f722d3..84792602cd 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials-auto-accept.test.ts @@ -49,7 +49,7 @@ describe('V2 Credentials - JSON-LD - Auto Accept Always', () => { })) await faberAgent.context.wallet.createKey({ - seed: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), keyType: KeyType.Ed25519, }) }) @@ -150,7 +150,7 @@ describe('V2 Credentials - JSON-LD - Auto Accept Always', () => { })) await faberAgent.context.wallet.createKey({ - seed: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), keyType: KeyType.Ed25519, }) }) diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index 03bd652904..0c7bd46567 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -175,7 +175,7 @@ describe('V2 Credentials - JSON-LD - Ed25519', () => { credentialDefinitionId = credentialDefinition.credentialDefinitionId await faberAgent.context.wallet.createKey({ - seed: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), keyType: KeyType.Ed25519, }) }) diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index 975c07e467..e792d1c32c 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -10,6 +10,7 @@ import { SubjectOutboundTransport } from '../../../../../../tests/transport/Subj import { getIndySdkModules } from '../../../../../indy-sdk/tests/setupIndySdkModule' import { getAgentOptions, waitForBasicMessage } from '../../../../tests/helpers' import { Agent } from '../../../agent/Agent' +import { sleep } from '../../../utils/sleep' import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' @@ -284,5 +285,7 @@ describe('mediator establishment', () => { }) expect(basicMessage.content).toBe(message) + + await recipientAgent.mediationRecipient.stopMessagePickup() }) }) diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts index 2633352dbf..ab3f31bd4f 100644 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts @@ -2,6 +2,7 @@ import type { IndySdkPool } from '../../ledger/IndySdkPool' import type { Wallet, DidRecord, RecordSavedEvent } from '@aries-framework/core' import { + TypedArrayEncoder, DidRepository, SigningProviderRegistry, JsonTransformer, @@ -54,7 +55,7 @@ describe('IndySdkSovDidRegistrar', () => { jest.clearAllMocks() }) - it('should return an error state if an invalid seed is provided', async () => { + it('should return an error state if an invalid private key is provided', async () => { const result = await indySdkSovDidRegistrar.create(agentContext, { method: 'sov', @@ -63,7 +64,7 @@ describe('IndySdkSovDidRegistrar', () => { alias: 'Hello', }, secret: { - seed: 'invalid', + privateKey: TypedArrayEncoder.fromString('invalid'), }, }) @@ -72,7 +73,7 @@ describe('IndySdkSovDidRegistrar', () => { didRegistrationMetadata: {}, didState: { state: 'failed', - reason: 'Invalid seed provided', + reason: 'Invalid private key provided', }, }) }) @@ -96,7 +97,7 @@ describe('IndySdkSovDidRegistrar', () => { alias: 'Hello', }, secret: { - seed: '12345678901234567890123456789012', + privateKey: TypedArrayEncoder.fromString('12345678901234567890123456789012'), }, }) @@ -130,7 +131,7 @@ describe('IndySdkSovDidRegistrar', () => { }) it('should correctly create a did:sov document without services', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' + const privateKey = '96213c3d7fc8d4d6754c712fd969598e' const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) @@ -143,7 +144,7 @@ describe('IndySdkSovDidRegistrar', () => { role: 'STEWARD', }, secret: { - seed, + privateKey: TypedArrayEncoder.fromString(privateKey), }, }) @@ -198,14 +199,14 @@ describe('IndySdkSovDidRegistrar', () => { keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], }, secret: { - seed, + privateKey, }, }, }) }) it('should correctly create a did:sov document with services', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' + const privateKey = '96213c3d7fc8d4d6754c712fd969598e' const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) @@ -226,7 +227,7 @@ describe('IndySdkSovDidRegistrar', () => { }, }, secret: { - seed, + privateKey: TypedArrayEncoder.fromString(privateKey), }, }) @@ -305,14 +306,14 @@ describe('IndySdkSovDidRegistrar', () => { keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], }, secret: { - seed, + privateKey, }, }, }) }) it('should store the did document', async () => { - const seed = '96213c3d7fc8d4d6754c712fd969598e' + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('did')) @@ -336,7 +337,7 @@ describe('IndySdkSovDidRegistrar', () => { }, }, secret: { - seed, + privateKey, }, }) diff --git a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts index eef3b5c8dd..de121b462d 100644 --- a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts @@ -117,7 +117,7 @@ describe('dids', () => { id: `did:sov:${indyDid}`, }, secret: { - privateKey, + privateKey: privateKey.toString(), }, }, }) From 804c938bcb5dcc416eac9080de0062f6edf7004f Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sun, 19 Feb 2023 20:01:51 +0100 Subject: [PATCH 21/22] fix: broken postgres types Signed-off-by: Timo Glastra --- packages/core/tests/helpers.ts | 9 +++--- .../tests/postgres.e2e.test.ts | 29 +++++++++---------- packages/node/src/PostgresPlugin.ts | 19 +++++++----- packages/node/src/index.ts | 8 ++--- 4 files changed, 32 insertions(+), 33 deletions(-) rename packages/{core => indy-sdk}/tests/postgres.e2e.test.ts (75%) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index f3c7f7306f..1ae3f39709 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -24,7 +24,7 @@ import path from 'path' import { lastValueFrom, firstValueFrom, ReplaySubject } from 'rxjs' import { catchError, filter, map, take, timeout } from 'rxjs/operators' -import { agentDependencies, WalletScheme } from '../../node/src' +import { agentDependencies, IndySdkPostgresWalletScheme } from '../../node/src' import { AgentConfig, AgentContext, @@ -91,18 +91,17 @@ export function getPostgresAgentOptions = {}, modules?: AgentModules ) { - const random = uuid().slice(0, 4) - const config: InitConfig = { label: `Agent: ${name}`, walletConfig: { - id: `Wallet: ${name} - ${random}`, + // NOTE: IndySDK Postgres database per wallet doesn't support special characters/spaces in the wallet name + id: `PostGresWallet${name}`, key: `Key${name}`, storage: { type: 'postgres_storage', config: { url: 'localhost:5432', - wallet_scheme: WalletScheme.DatabasePerWallet, + wallet_scheme: IndySdkPostgresWalletScheme.DatabasePerWallet, }, credentials: { account: 'postgres', diff --git a/packages/core/tests/postgres.e2e.test.ts b/packages/indy-sdk/tests/postgres.e2e.test.ts similarity index 75% rename from packages/core/tests/postgres.e2e.test.ts rename to packages/indy-sdk/tests/postgres.e2e.test.ts index 63dbd018f9..b8cda09b20 100644 --- a/packages/core/tests/postgres.e2e.test.ts +++ b/packages/indy-sdk/tests/postgres.e2e.test.ts @@ -1,18 +1,18 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' -import type { IndyPostgresStorageConfig } from '../../node/src' -import type { ConnectionRecord } from '../src/modules/connections' +import type { ConnectionRecord } from '../../core/src/modules/connections' +import type { IndySdkPostgresStorageConfig } from '../../node/src' import { Subject } from 'rxjs' import { SubjectInboundTransport } from '../../../tests/transport/SubjectInboundTransport' import { SubjectOutboundTransport } from '../../../tests/transport/SubjectOutboundTransport' -import { getIndySdkModules } from '../../indy-sdk/tests/setupIndySdkModule' -import { loadPostgresPlugin, WalletScheme } from '../../node/src' -import { Agent } from '../src/agent/Agent' -import { HandshakeProtocol } from '../src/modules/connections' +import { Agent } from '../../core/src/agent/Agent' +import { HandshakeProtocol } from '../../core/src/modules/connections' +import { waitForBasicMessage, getPostgresAgentOptions } from '../../core/tests/helpers' +import { loadIndySdkPostgresPlugin, IndySdkPostgresWalletScheme } from '../../node/src' -import { waitForBasicMessage, getPostgresAgentOptions } from './helpers' +import { getIndySdkModules } from './setupIndySdkModule' const alicePostgresAgentOptions = getPostgresAgentOptions( 'AgentsAlice', @@ -21,6 +21,7 @@ const alicePostgresAgentOptions = getPostgresAgentOptions( }, getIndySdkModules() ) + const bobPostgresAgentOptions = getPostgresAgentOptions( 'AgentsBob', { @@ -33,7 +34,6 @@ describe('postgres agents', () => { let aliceAgent: Agent let bobAgent: Agent let aliceConnection: ConnectionRecord - let bobConnection: ConnectionRecord afterAll(async () => { await bobAgent.shutdown() @@ -51,11 +51,11 @@ describe('postgres agents', () => { 'rxjs:bob': bobMessages, } - const storageConfig: IndyPostgresStorageConfig = { + const storageConfig: IndySdkPostgresStorageConfig = { type: 'postgres_storage', config: { url: 'localhost:5432', - wallet_scheme: WalletScheme.DatabasePerWallet, + wallet_scheme: IndySdkPostgresWalletScheme.DatabasePerWallet, }, credentials: { account: 'postgres', @@ -66,7 +66,7 @@ describe('postgres agents', () => { } // loading the postgres wallet plugin - loadPostgresPlugin(storageConfig.config, storageConfig.credentials) + loadIndySdkPostgresPlugin(storageConfig.config, storageConfig.credentials) aliceAgent = new Agent(alicePostgresAgentOptions) aliceAgent.registerInboundTransport(new SubjectInboundTransport(aliceMessages)) @@ -85,13 +85,10 @@ describe('postgres agents', () => { const { connectionRecord: bobConnectionAtBobAlice } = await bobAgent.oob.receiveInvitation( aliceBobOutOfBandRecord.outOfBandInvitation ) - bobConnection = await bobAgent.connections.returnWhenIsConnected(bobConnectionAtBobAlice!.id) + await bobAgent.connections.returnWhenIsConnected(bobConnectionAtBobAlice!.id) const [aliceConnectionAtAliceBob] = await aliceAgent.connections.findAllByOutOfBandId(aliceBobOutOfBandRecord.id) - aliceConnection = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionAtAliceBob!.id) - - expect(aliceConnection).toBeConnectedWith(bobConnection) - expect(bobConnection).toBeConnectedWith(aliceConnection) + await aliceAgent.connections.returnWhenIsConnected(aliceConnectionAtAliceBob!.id) }) test('send a message to connection', async () => { diff --git a/packages/node/src/PostgresPlugin.ts b/packages/node/src/PostgresPlugin.ts index 4ad7dc5f37..2bcac4aae2 100644 --- a/packages/node/src/PostgresPlugin.ts +++ b/packages/node/src/PostgresPlugin.ts @@ -70,32 +70,35 @@ type NativeIndyPostgres = { let indyPostgresStorage: NativeIndyPostgres | undefined -export interface WalletStorageConfig { +export interface IndySdkPostgresWalletStorageConfig { url: string - wallet_scheme: WalletScheme + wallet_scheme: IndySdkPostgresWalletScheme path?: string } -export interface WalletStorageCredentials { +export interface IndySdkPostgresWalletStorageCredentials { account: string password: string admin_account: string admin_password: string } -export enum WalletScheme { +export enum IndySdkPostgresWalletScheme { DatabasePerWallet = 'DatabasePerWallet', MultiWalletSingleTable = 'MultiWalletSingleTable', MultiWalletSingleTableSharedPool = 'MultiWalletSingleTableSharedPool', } -export interface IndyPostgresStorageConfig { +export interface IndySdkPostgresStorageConfig { type: 'postgres_storage' - config: WalletStorageConfig - credentials: WalletStorageCredentials + config: IndySdkPostgresWalletStorageConfig + credentials: IndySdkPostgresWalletStorageCredentials } -export function loadPostgresPlugin(config: WalletStorageConfig, credentials: WalletStorageCredentials) { +export function loadIndySdkPostgresPlugin( + config: IndySdkPostgresWalletStorageConfig, + credentials: IndySdkPostgresWalletStorageCredentials +) { if (!indyPostgresStorage) { indyPostgresStorage = getLibrary() } diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index a693aa2f9c..fbe7ed0452 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -5,7 +5,7 @@ import fetch from 'node-fetch' import WebSocket from 'ws' import { NodeFileSystem } from './NodeFileSystem' -import { IndyPostgresStorageConfig, loadPostgresPlugin, WalletScheme } from './PostgresPlugin' +import { IndySdkPostgresStorageConfig, loadIndySdkPostgresPlugin, IndySdkPostgresWalletScheme } from './PostgresPlugin' import { HttpInboundTransport } from './transport/HttpInboundTransport' import { WsInboundTransport } from './transport/WsInboundTransport' @@ -20,7 +20,7 @@ export { agentDependencies, HttpInboundTransport, WsInboundTransport, - loadPostgresPlugin, - IndyPostgresStorageConfig, - WalletScheme, + loadIndySdkPostgresPlugin, + IndySdkPostgresStorageConfig, + IndySdkPostgresWalletScheme, } From 133fdcacc093e17ebf6cf4f3e54c9e909382998f Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sun, 19 Feb 2023 22:45:07 +0100 Subject: [PATCH 22/22] test: fix broken test Signed-off-by: Timo Glastra --- packages/indy-sdk/tests/postgres.e2e.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/indy-sdk/tests/postgres.e2e.test.ts b/packages/indy-sdk/tests/postgres.e2e.test.ts index b8cda09b20..a59359f9d8 100644 --- a/packages/indy-sdk/tests/postgres.e2e.test.ts +++ b/packages/indy-sdk/tests/postgres.e2e.test.ts @@ -88,7 +88,7 @@ describe('postgres agents', () => { await bobAgent.connections.returnWhenIsConnected(bobConnectionAtBobAlice!.id) const [aliceConnectionAtAliceBob] = await aliceAgent.connections.findAllByOutOfBandId(aliceBobOutOfBandRecord.id) - await aliceAgent.connections.returnWhenIsConnected(aliceConnectionAtAliceBob!.id) + aliceConnection = await aliceAgent.connections.returnWhenIsConnected(aliceConnectionAtAliceBob!.id) }) test('send a message to connection', async () => {