diff --git a/demo/package.json b/demo/package.json index b41cae2066..247a4513cd 100644 --- a/demo/package.json +++ b/demo/package.json @@ -14,9 +14,15 @@ "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/indy-sdk": "^1.16.26", "@types/inquirer": "^8.1.3", "clear": "^0.1.0", "commander": "^8.3.0", diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index aa705ca7a4..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) + super({ port, name, useLegacyIndySdk: true }) this.connected = false } diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index abf507014e..26429ca358 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -1,7 +1,34 @@ +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, + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, + V1CredentialProtocol, + V1ProofProtocol, +} from '@aries-framework/anoncreds' +import { AnonCredsRsModule } from '@aries-framework/anoncreds-rs' +import { AskarModule } from '@aries-framework/askar' +import { + TypedArrayEncoder, + KeyType, + DidsModule, + V2ProofProtocol, + V2CredentialProtocol, + ProofsModule, + AutoAcceptProof, + AutoAcceptCredential, + CredentialsModule, + Agent, + HttpOutboundTransport, +} from '@aries-framework/core' +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' @@ -10,46 +37,163 @@ 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 = { + // 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 useLegacyIndySdk: boolean - public constructor(port: number, name: string) { + public constructor({ + port, + name, + useLegacyIndySdk = false, + }: { + port: number + name: string + useLegacyIndySdk?: boolean + }) { 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, - }, - ], + publicDidSeed: 'afjdemoverysercure00000000000000', 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 }) + // TODO: do not hardcode this + this.anonCredsIssuerId = '2jEvRuKmfBJTRa7QowDpNN' + this.useLegacyIndySdk = useLegacyIndySdk + + this.agent = new Agent({ + config, + dependencies: agentDependencies, + modules: useLegacyIndySdk ? getLegacyIndySdkModules() : getAskarAnonCredsIndyModules(), + }) this.agent.registerInboundTransport(new HttpInboundTransport({ port })) this.agent.registerOutboundTransport(new HttpOutboundTransport()) } 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. + if (!this.useLegacyIndySdk) { + try { + await this.agent.context.wallet.createKey({ + keyType: KeyType.Ed25519, + privateKey: TypedArrayEncoder.fromString('afjdemoverysercure00000000000000'), + }) + } catch (error) { + // We assume the key already exists, and that's why askar failed + } + } + console.log(greenText(`\nAgent ${this.name} created!\n`)) } } + +function getAskarAnonCredsIndyModules() { + 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 getLegacyIndySdkModules() { + 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..4585f82c7d 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 }) 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/package.json b/package.json index 582f91a77e..8f5fee182f 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,11 @@ "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", "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/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/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-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/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/anoncreds/src/AnonCredsApi.ts b/packages/anoncreds/src/AnonCredsApi.ts index b52f4dbc0f..9e56a51ea5 100644 --- a/packages/anoncreds/src/AnonCredsApi.ts +++ b/packages/anoncreds/src/AnonCredsApi.ts @@ -1,5 +1,7 @@ -import type { AnonCredsCreateLinkSecretOptions } from './AnonCredsApiOptions' -import type { AnonCredsCredentialDefinition } from './models' +import type { + AnonCredsCreateLinkSecretOptions, + AnonCredsRegisterCredentialDefinitionOptions, +} from './AnonCredsApiOptions' import type { GetCredentialDefinitionReturn, GetRevocationStatusListReturn, @@ -207,7 +209,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/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/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts index 93e2151870..af8ee049c8 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -30,13 +30,13 @@ import type { } from '@aries-framework/core' import { + ProblemReportError, MessageValidator, CredentialFormatSpec, AriesFrameworkError, 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}` ) } @@ -315,6 +314,10 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic credentialRevocationId: credentialRevocationId, revocationRegistryId: credential.rev_reg_id, }) + credentialRecord.setTags({ + anonCredsRevocationRegistryId: credential.rev_reg_id, + anonCredsCredentialRevocationId: credentialRevocationId, + }) } const format = new CredentialFormatSpec({ @@ -344,9 +347,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}` ) } @@ -415,7 +417,11 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { credentialRevocationId: credential.credentialRevocationId, - revocationRegistryId: anonCredsCredential.rev_reg_id, + revocationRegistryId: credential.revocationRegistryId, + }) + credentialRecord.setTags({ + anonCredsRevocationRegistryId: credential.revocationRegistryId, + anonCredsCredentialRevocationId: credential.credentialRevocationId, }) } 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 b75df46b52..c2e5e2d1d5 100644 --- a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -38,7 +38,7 @@ import type { ProofFormatSelectCredentialsForRequestReturn, ProofFormatAutoRespondProposalOptions, ProofFormatAutoRespondRequestOptions, - IndyGetCredentialsForProofRequestOptions, + ProofFormatAutoRespondPresentationOptions, } from '@aries-framework/core' import { @@ -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 { + 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 @@ -333,7 +332,7 @@ export class LegacyIndyProofFormatService implements ProofFormatService { const credentialsForProofRequest: AnonCredsCredentialsForProofRequest = { attributes: {}, 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 60359bb3ae..33a306617a 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 @@ -34,7 +34,7 @@ const anonCredsModuleConfig = new AnonCredsModuleConfig({ registries: [registry], }) -const agentConfig = getAgentConfig('LegacyIndyProofFormatServiceTest') +const agentConfig = getAgentConfig('LegacyIndyFormatServicesTest') const anonCredsRevocationService = new IndySdkRevocationService(indySdk) const anonCredsVerifierService = new IndySdkVerifierService(indySdk) const anonCredsHolderService = new IndySdkHolderService(anonCredsRevocationService, indySdk) diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts index ced98385f2..11e113699c 100644 --- a/packages/anoncreds/src/index.ts +++ b/packages/anoncreds/src/index.ts @@ -3,8 +3,9 @@ export * from './services' export * from './error' export * from './repository' export * from './formats' +export * from './protocols' export { AnonCredsModule } from './AnonCredsModule' export { AnonCredsModuleConfig, AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' export { AnonCredsApi } from './AnonCredsApi' -export { AnonCredsCreateLinkSecretOptions } from './AnonCredsApiOptions' +export * from './AnonCredsApiOptions' diff --git a/packages/anoncreds/src/models/AnonCredsProofRequest.ts b/packages/anoncreds/src/models/AnonCredsProofRequest.ts index 2f57e32af3..3448b71570 100644 --- a/packages/anoncreds/src/models/AnonCredsProofRequest.ts +++ b/packages/anoncreds/src/models/AnonCredsProofRequest.ts @@ -1,7 +1,6 @@ import type { AnonCredsRequestedAttributeOptions } from './AnonCredsRequestedAttribute' 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' @@ -73,10 +72,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/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/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts similarity index 89% rename from packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts rename to packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts index 1c0320070f..12f464d3f3 100644 --- a/packages/core/src/modules/credentials/protocol/v1/V1CredentialProtocol.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/V1CredentialProtocol.ts @@ -1,82 +1,75 @@ -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, + utils, + DidCommMessageRepository, + DidCommMessageRole, + BaseCredentialProtocol, + isLinkedAttachment, +} from '@aries-framework/core' + +import { AnonCredsCredentialProposal } from '../../../models/AnonCredsCredentialProposal' +import { composeCredentialAutoAccept, areCredentialPreviewAttributesEqual } from '../../../utils' 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() + // TODO: just create a new instance of LegacyIndyCredentialFormatService here so it makes the setup easier this.indyCredentialFormat = indyCredentialFormat } @@ -123,8 +116,8 @@ export class V1CredentialProtocol credentialFormats, comment, autoAcceptCredential, - }: CreateCredentialProposalOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.CreateCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { this.assertOnlyIndyFormat(credentialFormats) const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) @@ -141,7 +134,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, @@ -155,7 +148,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 +284,8 @@ export class V1CredentialProtocol credentialFormats, comment, autoAcceptCredential, - }: AcceptCredentialProposalOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.AcceptCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) @@ -364,8 +357,8 @@ export class V1CredentialProtocol credentialRecord, comment, autoAcceptCredential, - }: NegotiateCredentialProposalOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.NegotiateCredentialProposalOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.ProposalReceived) @@ -421,8 +414,8 @@ export class V1CredentialProtocol autoAcceptCredential, comment, connectionRecord, - }: CreateCredentialOfferOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.CreateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert this.assertOnlyIndyFormat(credentialFormats) @@ -436,7 +429,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 ), @@ -587,8 +580,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 +647,8 @@ export class V1CredentialProtocol credentialRecord, autoAcceptCredential, comment, - }: NegotiateCredentialOfferOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.NegotiateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.OfferReceived) @@ -683,7 +676,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,21 +712,12 @@ 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') } - /** - * Process a received {@link IssueCredentialMessage}. This will not accept the credential - * or send a credential acknowledgement. It will only update the existing credential record with - * the information from the issue credential message. Use {@link createAck} - * after calling this method to create a credential acknowledgement. - * - * @param messageContext The message context containing an issue credential message - * - * @returns credential record associated with the issue credential message - * - */ public async processRequest( messageContext: InboundMessageContext ): Promise { @@ -796,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 * @@ -808,8 +792,8 @@ export class V1CredentialProtocol credentialFormats, comment, autoAcceptCredential, - }: AcceptCredentialRequestOptions<[IndyCredentialFormatServiceLike]> - ): Promise> { + }: CredentialProtocolOptions.AcceptCredentialRequestOptions<[LegacyIndyCredentialFormatService]> + ): Promise> { // Assert credentialRecord.assertProtocolVersion('v1') credentialRecord.assertState(CredentialState.RequestReceived) @@ -865,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 @@ -943,8 +927,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 +1001,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, @@ -1041,7 +1025,7 @@ export class V1CredentialProtocol const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1063,7 +1047,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 ) @@ -1080,7 +1064,7 @@ export class V1CredentialProtocol const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1102,7 +1086,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 ) @@ -1119,7 +1103,7 @@ export class V1CredentialProtocol const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1154,7 +1138,7 @@ export class V1CredentialProtocol const credentialsModuleConfig = agentContext.dependencyManager.resolve(CredentialsModuleConfig) - const autoAccept = composeAutoAccept( + const autoAccept = composeCredentialAutoAccept( credentialRecord.autoAcceptCredential, credentialsModuleConfig.autoAcceptCredentials ) @@ -1221,7 +1205,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 +1253,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 84% 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..eb255070cc 100644 --- a/packages/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolCred.test.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolCred.test.ts @@ -1,37 +1,38 @@ -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, + DidExchangeState, + Attachment, + AttachmentData, + JsonEncoder, + DidCommMessageRecord, + DidCommMessageRole, + AriesFrameworkError, + CredentialState, + CredentialExchangeRecord, + 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 { 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' import { V1CredentialProtocol } from '../V1CredentialProtocol' import { INDY_CREDENTIAL_ATTACHMENT_ID, @@ -47,25 +48,26 @@ 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 -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 +92,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 +101,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 +131,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 } @@ -150,35 +152,29 @@ 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, id, credentialAttributes, - indyRevocationRegistryId, - indyCredentialRevocationId, }: { state?: CredentialState - metadata?: IndyCredentialViewMetadata & { indyRequest: Record } tags?: CustomCredentialTags threadId?: string connectionId?: string credentialId?: string id?: string credentialAttributes?: CredentialPreviewAttribute[] - indyRevocationRegistryId?: string - indyCredentialRevocationId?: string } = {}) => { const credentialRecord = new CredentialExchangeRecord({ id, credentialAttributes: credentialAttributes || credentialPreview.attributes, state: state || CredentialState.OfferSent, - threadId: threadId ?? uuid(), + threadId: threadId ?? '809dd7ec-f0e7-4b97-9231-7a3615af6139', connectionId: connectionId ?? '123', credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: '123456', }, ], @@ -186,29 +182,6 @@ const mockCredentialRecord = ({ protocolVersion: 'v1', }) - 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, - }) - } - - if (indyCredentialRevocationId || indyRevocationRegistryId) { - credentialRecord.metadata.add(CredentialMetadataKeys.IndyCredential, { - indyCredentialRevocationId, - indyRevocationRegistryId, - }) - } - return credentialRecord } @@ -243,7 +216,7 @@ describe('V1CredentialProtocol', () => { didCommMessageRecord, ]) - credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: indyCredentialFormatService }) + credentialProtocol = new V1CredentialProtocol({ indyCredentialFormat: legacyIndyCredentialFormatService }) }) afterEach(() => { @@ -259,14 +232,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 +246,6 @@ describe('V1CredentialProtocol', () => { comment: 'hello', autoAcceptCredential: AutoAcceptCredential.Never, credentialRecord, - credentialFormats, }) // then @@ -298,15 +264,10 @@ 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, - credentialFormats: { - indy: { - holderDid: 'did:sov:123456789abcdefghi', - }, - }, }) expect(didCommMessageRepository.saveOrUpdateAgentMessage).toHaveBeenCalledWith(agentContext, { agentMessage: message, @@ -323,7 +284,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 +390,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 +420,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 +460,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 +483,7 @@ describe('V1CredentialProtocol', () => { '~please_ack': expect.any(Object), }) - expect(indyCredentialFormatService.acceptRequest).toHaveBeenCalledWith(agentContext, { + expect(legacyIndyCredentialFormatService.acceptRequest).toHaveBeenCalledWith(agentContext, { credentialRecord, requestAttachment, offerAttachment, @@ -561,7 +522,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 +801,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 +819,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 +833,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 +847,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/core/src/modules/credentials/protocol/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts b/packages/anoncreds/src/protocols/credentials/v1/__tests__/V1CredentialProtocolProposeOffer.test.ts similarity index 79% 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..f4a72ce08c 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,55 +1,44 @@ -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { CreateCredentialOfferOptions, CreateCredentialProposalOptions } from '../../CredentialProtocolOptions' - +import type { CredentialProtocolOptions, CredentialStateChangedEvent } from '@aries-framework/core' + +import { + EventEmitter, + DidExchangeState, + Attachment, + AttachmentData, + CredentialState, + CredentialFormatSpec, + CredentialExchangeRecord, + CredentialEventTypes, + JsonTransformer, + InboundMessageContext, +} from '@aries-framework/core' 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 { 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 { INDY_CREDENTIAL_OFFER_ATTACHMENT_ID, V1OfferCredentialMessage } from '../messages' -import { V1CredentialPreview } from '../messages/V1CredentialPreview' +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('../../../../../storage/didcomm/DidCommMessageRepository') -jest.mock('../../../../routing/services/RoutingService') -jest.mock('../../../../connections/services/ConnectionService') -jest.mock('../../../../../agent/Dispatcher') +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 -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 -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 indyLedgerService = new IndyLedgerServiceMock() -const indyCredentialFormatService = new IndyCredentialFormatServiceMock() -const dispatcher = new DispatcherMock() const connectionService = new ConnectionServiceMock() +const indyCredentialFormatService = new LegacyIndyCredentialFormatServiceMock() const agentConfig = getAgentConfig('V1CredentialProtocolProposeOfferTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -58,8 +47,6 @@ const agentContext = getAgentContext({ registerInstances: [ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], - [RoutingService, routingService], - [Dispatcher, dispatcher], [ConnectionService, connectionService], [EventEmitter, eventEmitter], ], @@ -68,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', @@ -108,8 +95,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, @@ -121,7 +106,9 @@ describe('V1CredentialProtocolProposeOffer', () => { }) describe('createProposal', () => { - const proposeOptions: CreateCredentialProposalOptions<[IndyCredentialFormatService]> = { + const proposeOptions: CredentialProtocolOptions.CreateCredentialProposalOptions< + [LegacyIndyCredentialFormatService] + > = { connectionRecord: connectionRecord, credentialFormats: { indy: { @@ -234,7 +221,7 @@ describe('V1CredentialProtocolProposeOffer', () => { }) describe('createOffer', () => { - const offerOptions: CreateCredentialOfferOptions<[IndyCredentialFormatService]> = { + const offerOptions: CredentialProtocolOptions.CreateCredentialOfferOptions<[LegacyIndyCredentialFormatService]> = { comment: 'some comment', connectionRecord, credentialFormats: { 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 66% 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..975db00a6e 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 @@ -1,27 +1,12 @@ -import type { SubjectMessage } from '../../../../../../../../tests/transport/SubjectInboundTransport' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '../../../CredentialsApiOptions' - -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 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' - -const faberAgentOptions = getAgentOptions('Faber connection-less Credentials V1', { - endpoints: ['rxjs:faber'], -}) +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' +import type { AcceptCredentialOfferOptions, AcceptCredentialRequestOptions } from '@aries-framework/core' -const aliceAgentOptions = getAgentOptions('Alice connection-less Credentials V1', { - endpoints: ['rxjs:alice'], -}) +import { AutoAcceptCredential, CredentialExchangeRecord, CredentialState } from '@aries-framework/core' + +import { waitForCredentialRecordSubject, testLogger } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../messages' const credentialPreview = V1CredentialPreview.fromRecord({ name: 'John', @@ -29,42 +14,27 @@ const credentialPreview = V1CredentialPreview.fromRecord({ }) 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 + let schemaId: 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 { definition } = await prepareForIssuance(faberAgent, ['name', 'age']) - credentialDefinitionId = definition.id - - faberReplay = new ReplaySubject() - aliceReplay = new ReplaySubject() - - faberAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(faberReplay) - aliceAgent.events - .observable(CredentialEventTypes.CredentialStateChanged) - .subscribe(aliceReplay) + ;({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + schemaId, + } = await setupAnonCredsTests({ + issuerName: 'Faber connection-less Credentials V1', + holderName: 'Alice connection-less Credentials V1', + attributeNames: ['name', 'age'], + createConnections: false, + })) }) afterEach(async () => { @@ -144,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), }, ], @@ -165,7 +136,8 @@ describe('V1 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { + schemaId, credentialDefinitionId, }, }, @@ -225,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/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 58% 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 index 42da4ed4da..6c0455755c 100644 --- 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 @@ -1,44 +1,52 @@ -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 { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +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' + +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('Auto accept on `always`', () => { +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 + 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, + issuerReplay: faberReplay, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + credentialDefinitionId, + schemaId, + issuerHolderConnectionId: faberConnectionId, + holderIssuerConnectionId: aliceConnectionId, + } = await setupAnonCredsTests({ + issuerName: 'Faber Credentials Auto Accept V1', + holderName: 'Alice Credentials Auto Accept V1', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + autoAcceptCredentials: AutoAcceptCredential.Always, + })) }) afterAll(async () => { @@ -48,16 +56,16 @@ describe('credentials', () => { 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({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', @@ -81,9 +89,9 @@ describe('credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { - schemaId: schema.id, - credentialDefinitionId: credDefId, + '_anonCreds/anonCredsCredential': { + schemaId: schemaId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -91,27 +99,26 @@ describe('credentials', () => { }) }) - 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 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', }) 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, }) @@ -121,16 +128,16 @@ describe('credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -145,13 +152,23 @@ describe('credentials', () => { }) }) - describe('Auto accept on `contentApproved`', () => { + 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, + issuerReplay: faberReplay, + 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', 'x-ray', 'profile_picture'], + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + })) }) afterAll(async () => { @@ -164,51 +181,45 @@ describe('credentials', () => { // ============================== // 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') - 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 waitForCredentialRecordSubject(faberReplay, { 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, { + 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, }) @@ -219,16 +230,16 @@ describe('credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyRequest': expect.any(Object), - '_internal/indyCredential': { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -241,9 +252,9 @@ describe('credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { schemaId, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -251,26 +262,22 @@ describe('credentials', () => { }) }) - 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') - 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 waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -284,78 +291,73 @@ describe('credentials', () => { expect(aliceCredentialExchangeRecord.getTags()).toEqual({ threadId: aliceCredentialExchangeRecord.threadId, state: aliceCredentialExchangeRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, 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: credDefId, - }, + 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: { + '_anonCreds/anonCredsCredentialRequest': expect.any(Object), + '_anonCreds/anonCredsCredential': { + 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: 'anoncreds', + 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 () => { + 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', - 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') - let aliceCredentialExchangeRecord = await waitForCredentialRecord(aliceAgent, { + let aliceCredentialExchangeRecord = await waitForCredentialRecordSubject(aliceReplay, { threadId: faberCredentialExchangeRecord.threadId, state: CredentialState.OfferReceived, }) @@ -365,7 +367,7 @@ describe('credentials', () => { expect(aliceCredentialExchangeRecord.getTags()).toEqual({ threadId: aliceCredentialExchangeRecord.threadId, state: aliceCredentialExchangeRecord.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) @@ -375,7 +377,7 @@ describe('credentials', () => { credentialFormats: { indy: { attributes: newCredentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', @@ -395,22 +397,22 @@ describe('credentials', () => { 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: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v1', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v1 propose credential test', }) 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, }) @@ -419,7 +421,7 @@ describe('credentials', () => { credentialRecordId: faberCredentialExchangeRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -427,7 +429,7 @@ describe('credentials', () => { 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, }) @@ -437,7 +439,7 @@ describe('credentials', () => { expect(record.getTags()).toEqual({ threadId: record.threadId, state: record.state, - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, credentialIds: [], }) 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 86% 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 index 1d25498a3a..78a6f2f852 100644 --- 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 @@ -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' + +import { waitForCredentialRecord } from '../../../../../../core/tests/helpers' +import testLogger from '../../../../../../core/tests/logger' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' import { V1ProposeCredentialMessage, V1RequestCredentialMessage, @@ -15,19 +18,23 @@ 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', + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) }) afterAll(async () => { @@ -48,7 +55,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 +72,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 +90,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 +159,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/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts b/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts new file mode 100644 index 0000000000..113a8ac6f2 --- /dev/null +++ b/packages/anoncreds/src/protocols/credentials/v1/errors/V1CredentialProblemReportError.ts @@ -0,0 +1,23 @@ +import type { ProblemReportErrorOptions, CredentialProblemReportReason } from '@aries-framework/core' + +import { 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/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts similarity index 94% 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..b4d3384ed9 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialAckHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialAckHandler.ts @@ -1,5 +1,5 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1CredentialAckMessage } from '../messages' 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 94% 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..b2e599577d 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1CredentialProblemReportHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1CredentialProblemReportHandler.ts @@ -1,5 +1,5 @@ -import type { MessageHandler, MessageHandlerInboundMessage } from '../../../../../agent/MessageHandler' import type { V1CredentialProtocol } from '../V1CredentialProtocol' +import type { MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' import { V1CredentialProblemReportMessage } from '../messages' 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 88% rename from packages/core/src/modules/credentials/protocol/v1/handlers/V1IssueCredentialHandler.ts rename to packages/anoncreds/src/protocols/credentials/v1/handlers/V1IssueCredentialHandler.ts index 82e7dea44a..e828fb2258 100644 --- a/packages/core/src/modules/credentials/protocol/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/core/src/modules/credentials/protocol/v1/handlers/V1OfferCredentialHandler.ts b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1OfferCredentialHandler.ts similarity index 82% 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..8d2d847e96 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 { @@ -54,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/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 88% 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..00154fc8a4 100644 --- a/packages/core/src/modules/credentials/protocol/v1/handlers/V1RequestCredentialHandler.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/handlers/V1RequestCredentialHandler.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 { CredentialExchangeRecord, MessageHandler, MessageHandlerInboundMessage } from '@aries-framework/core' + +import { DidCommMessageRepository, DidCommMessageRole, OutboundMessageContext } from '@aries-framework/core' -import { OutboundMessageContext } from '../../../../../agent/models' -import { DidCommMessageRepository, DidCommMessageRole } from '../../../../../storage' import { V1RequestCredentialMessage } from '../messages' export class V1RequestCredentialHandler implements MessageHandler { 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..db9bba955d 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,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 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..7b3a3c4c06 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,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 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 7879222141..a8727a355a 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,8 @@ -import type { Cred } from 'indy-sdk' +import type { AnonCredsCredential } 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, IsArray, ValidateNested, IsInstance } from 'class-validator' export const INDY_CREDENTIAL_ATTACHMENT_ID = 'libindy-cred-0' @@ -45,11 +42,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 51f19b24de..abb58f9aa1 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 86% 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..7c50693cad 100644 --- a/packages/core/src/modules/credentials/protocol/v1/messages/V1ProposeCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts @@ -1,11 +1,15 @@ -import type { Attachment } from '../../../../../decorators/attachment/Attachment' +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 { AgentMessage } from '../../../../../agent/AgentMessage' -import { indyDidRegex, schemaIdRegex, schemaVersionRegex, credDefIdRegex } from '../../../../../utils' -import { IsValidMessageType, parseMessageType } from '../../../../../utils/messageType' +import { + legacyIndyCredentialDefinitionIdRegex, + legacyIndyDidRegex, + legacyIndySchemaIdRegex, + legacyIndySchemaVersionRegex, +} from '../../../../utils' import { V1CredentialPreview } from './V1CredentialPreview' @@ -73,7 +77,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'schema_issuer_did' }) @IsString() @IsOptional() - @Matches(indyDidRegex) + @Matches(legacyIndyDidRegex) public schemaIssuerDid?: string /** @@ -82,7 +86,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'schema_id' }) @IsString() @IsOptional() - @Matches(schemaIdRegex) + @Matches(legacyIndySchemaIdRegex) public schemaId?: string /** @@ -99,7 +103,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 @@ -110,7 +114,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'cred_def_id' }) @IsString() @IsOptional() - @Matches(credDefIdRegex) + @Matches(legacyIndyCredentialDefinitionIdRegex) public credentialDefinitionId?: string /** @@ -119,6 +123,6 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'issuer_did' }) @IsString() @IsOptional() - @Matches(indyDidRegex) + @Matches(legacyIndyDidRegex) public issuerDid?: string } 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/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 83% 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 index 0b1febb680..d3c6a4f204 100644 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/V1ProofProtocol.test.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/V1ProofProtocol.test.ts @@ -1,37 +1,38 @@ -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 { + DidExchangeState, + Attachment, + AttachmentData, + ProofState, + ProofExchangeRecord, + InboundMessageContext, + ProofEventTypes, + 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/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 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 new file mode 100644 index 0000000000..df66a6217d --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-connectionless-proofs.e2e.test.ts @@ -0,0 +1,433 @@ +import type { SubjectMessage } from '../../../../../../../tests/transport/SubjectInboundTransport' + +import { Subject } from 'rxjs' + +import { SubjectInboundTransport } from '../../../../../../../tests/transport/SubjectInboundTransport' +import { SubjectOutboundTransport } from '../../../../../../../tests/transport/SubjectOutboundTransport' +import { + CredentialEventTypes, + Agent, + AutoAcceptProof, + ProofState, + HandshakeProtocol, + MediatorPickupStrategy, + LinkedAttachment, + Attachment, + AttachmentData, + ProofEventTypes, +} from '../../../../../../core/src' +import { uuid } from '../../../../../../core/src/utils/uuid' +import { + testLogger, + waitForProofExchangeRecordSubject, + getAgentOptions, + makeConnection, + setupEventReplaySubjects, +} from '../../../../../../core/tests' +import { getIndySdkModules } from '../../../../../../indy-sdk/tests/setupIndySdkModule' +import { + getLegacyAnonCredsModules, + issueLegacyAnonCredsCredential, + prepareForAnonCredsIssuance, + setupAnonCredsTests, +} from '../../../../../tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../../../credentials/v1' + +describe('V1 Proofs - Connectionless - Indy', () => { + let agents: Agent[] + + afterEach(async () => { + for (const agent of agents) { + await agent.shutdown() + await agent.wallet.delete() + } + }) + + test('Faber starts with connection-less proof requests to Alice', async () => { + 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'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerReplay: faberReplay, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + 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', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + 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, + }, + ], + }, + }, + }, + }, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + testLogger.test('Alice waits for presentation request from Faber') + 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, + }) + + await aliceAgent.proofs.acceptRequest({ + proofRecordId: aliceProofExchangeRecord.id, + proofFormats: { indy: requestedCredentials.proofFormats.indy }, + }) + + testLogger.test('Faber waits for presentation from Alice') + faberProofExchangeRecord = await waitForProofExchangeRecordSubject(faberReplay, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.PresentationReceived, + }) + + // assert presentation is valid + expect(faberProofExchangeRecord.isVerified).toBe(true) + + // Faber accepts presentation + await faberAgent.proofs.acceptPresentation({ proofRecordId: faberProofExchangeRecord.id }) + + // Alice waits till it receives presentation ack + 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 () => { + 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'], + }) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + issuerReplay: faberReplay, + holderReplay: aliceReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + value: '99', + }, + ], + }, + }) + + agents = [aliceAgent, faberAgent] + + const { message, proofRecord: faberProofExchangeRecord } = await faberAgent.proofs.createRequest({ + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + 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, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + await waitForProofExchangeRecordSubject(aliceReplay, { + state: ProofState.Done, + }) + + 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 () => { + testLogger.test('Faber sends presentation request to Alice') + + const credentialPreview = V1CredentialPreview.fromRecord({ + name: 'John', + age: '99', + }) + + const unique = uuid().substring(0, 4) + + const mediatorAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Mediator-${unique}`, + { + autoAcceptMediationRequests: true, + endpoints: ['rxjs:mediator'], + }, + getIndySdkModules() + ) + + const mediatorMessages = new Subject() + const subjectMap = { 'rxjs:mediator': mediatorMessages } + + // Initialize mediator + const mediatorAgent = new Agent(mediatorAgentOptions) + mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) + await mediatorAgent.initialize() + + const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ + label: 'faber invitation', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const aliceMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ + label: 'alice invitation', + handshakeProtocols: [HandshakeProtocol.Connections], + }) + + const faberAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Faber-${unique}`, + { + mediatorConnectionsInvite: faberMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) + ) + + const aliceAgentOptions = getAgentOptions( + `Connectionless proofs with mediator Alice-${unique}`, + { + mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) + ) + + const faberAgent = new Agent(faberAgentOptions) + faberAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await faberAgent.initialize() + + const aliceAgent = new Agent(aliceAgentOptions) + aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) + await aliceAgent.initialize() + + const [faberReplay, aliceReplay] = setupEventReplaySubjects( + [faberAgent, aliceAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + + agents = [aliceAgent, faberAgent, mediatorAgent] + + 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) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnection.id, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + offer: { + credentialDefinitionId: credentialDefinition.credentialDefinitionId, + 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 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({ + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'test-proof-request', + version: '1.0', + 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, + }) + + const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ + recordId: faberProofExchangeRecord.id, + message, + domain: 'https://a-domain.com', + }) + + const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() + if (!mediationRecord) { + throw new Error('Faber agent has no default mediator') + } + + expect(requestMessage).toMatchObject({ + service: { + recipientKeys: [expect.any(String)], + routingKeys: mediationRecord.routingKeys, + serviceEndpoint: mediationRecord.endpoint, + }, + }) + + await aliceAgent.receiveMessage(requestMessage.toJSON()) + + await aliceProofExchangeRecordPromise + + await faberProofExchangeRecordPromise + + await aliceAgent.mediationRecipient.stopMessagePickup() + await faberAgent.mediationRecipient.stopMessagePickup() + }) +}) 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.e2e.test.ts similarity index 53% 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.e2e.test.ts index e7f7a6f5f1..917f5c805d 100644 --- 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.e2e.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,48 +41,44 @@ 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), comment: 'V1 propose proof test 1', presentationProposal: { type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], + attributes: [], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', - threshold: 50, + threshold: 18, }, ], }, @@ -101,70 +90,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 +167,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,33 +184,20 @@ 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), comment: 'V1 propose proof test 2', presentationProposal: { type: 'https://didcomm.org/present-proof/1.0/presentation-preview', - attributes: [ - { - name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, - value: 'John', - referent: '0', - }, - ], + attributes: [], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', - threshold: 50, + threshold: 18, }, ], }, @@ -264,13 +225,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), @@ -301,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: presentationPreview.attributes[0].credentialDefinitionId, - value: 'John', - referent: '0', - }, - ], + attributes: [], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', - threshold: 50, + threshold: 18, }, ], }, @@ -328,23 +276,14 @@ describe('Present Proof', () => { expect(proofRequestMessage.indyProofRequest).toMatchObject({ name: 'Proof Request', version: '1.0', - requested_attributes: { - '0': { - name: 'name', - restrictions: [ - { - cred_def_id: credDefId, - }, - ], - }, - }, + requested_attributes: {}, requested_predicates: { [predicateKey]: { p_type: '>=', - p_value: 50, + p_value: 18, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, 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.e2e.test.ts similarity index 60% 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.e2e.test.ts index 86095b8f01..5b4c358a2f 100644 --- 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.e2e.test.ts @@ -1,29 +1,55 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections/repository/ConnectionRecord' -import type { V1PresentationPreview } from '../models/V1PresentationPreview' +import type { EventReplaySubject } from '../../../../../../core/tests' +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 { issueLegacyAnonCredsCredential, 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 faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let aliceConnectionId: string + let faberConnectionId: 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, + 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 () => { @@ -37,19 +63,33 @@ 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, + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], }, }, comment: 'V1 propose proof test', @@ -57,15 +97,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 +109,15 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -100,11 +130,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 +145,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 +168,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 +187,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), @@ -184,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), }, @@ -204,10 +211,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.e2e.test.ts b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.e2e.test.ts new file mode 100644 index 0000000000..14e9e72145 --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-indy-proof-proposal.e2e.test.ts @@ -0,0 +1,106 @@ +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +import { ProofState } from '../../../../../../core/src' +import { testLogger, waitForProofExchangeRecord } from '../../../../../../core/tests' +import { setupAnonCredsTests } from '../../../../../tests/legacyAnonCredsSetup' + +describe('Present Proof', () => { + let faberAgent: AnonCredsTestsAgent + let aliceAgent: AnonCredsTestsAgent + let aliceConnectionId: string + let credentialDefinitionId: string + + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + 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 () => { + 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 faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { + state: ProofState.ProposalReceived, + }) + + await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'ProofRequest', + version: '1.0', + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], + }, + }, + comment: 'V1 propose proof test', + }) + + testLogger.test('Faber waits for presentation from Alice') + 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), + comment: 'V1 propose proof test', + presentationProposal: { + type: 'https://didcomm.org/present-proof/1.0/presentation-preview', + attributes: [ + { + name: 'name', + credentialDefinitionId, + value: 'John', + referent: '0', + }, + ], + predicates: [ + { + name: 'age', + credentialDefinitionId, + predicate: '>=', + threshold: 50, + }, + ], + }, + }) + + expect(faberProofExchangeRecord).toMatchObject({ + id: expect.anything(), + threadId: faberProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + protocolVersion: 'v1', + }) + }) +}) 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 59% 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 index 8c9278b879..36e9203b0d 100644 --- 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 @@ -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 () => { @@ -31,53 +32,63 @@ 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, { 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, + referent: '0', + }, + ], + 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({ - 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: presentationPreview.attributes[0].credentialDefinitionId, + cred_def_id: credentialDefinitionId, value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + cred_def_id: credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -90,9 +101,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/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 72% 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 index 918673b0b3..ff71996463 100644 --- 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 @@ -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 Proofs V1 - Full', + holderName: 'Alice Proofs V1 - Full', + 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,34 @@ 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, + referent: '0', + }, + ], + 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 +100,15 @@ describe('Present Proof', () => { attributes: [ { name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, + credentialDefinitionId, value: 'John', referent: '0', }, - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, ], predicates: [ { name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, + credentialDefinitionId, predicate: '>=', threshold: 50, }, @@ -159,15 +189,6 @@ describe('Present Proof', () => { }, }, ], - // appendedAttachments: [ - // { - // id: expect.any(String), - // filename: expect.any(String), - // data: { - // base64: expect.any(String), - // }, - // }, - // ], thread: { threadId: expect.any(String), }, @@ -222,8 +243,8 @@ describe('Present Proof', () => { const formatData = await aliceAgent.proofs.getFormatData(aliceProofExchangeRecord.id) - // eslint-disable-next-line prefer-const - let { proposeKey1, proposeKey2, requestKey1, requestKey2 } = getGroupKeysFromIndyProofFormatData(formatData) + const proposalPredicateKey = Object.keys(formatData.proposal?.indy?.requested_predicates || {})[0] + const requestPredicateKey = Object.keys(formatData.request?.indy?.requested_predicates || {})[0] expect(formatData).toMatchObject({ proposal: { @@ -235,23 +256,15 @@ describe('Present Proof', () => { 0: { name: 'name', }, - [proposeKey1]: { - name: 'image_0', - restrictions: [ - { - cred_def_id: credDefId, - }, - ], - }, }, requested_predicates: { - [proposeKey2]: { + [proposalPredicateKey]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -267,23 +280,15 @@ describe('Present Proof', () => { 0: { name: 'name', }, - [requestKey1]: { - name: 'image_0', - restrictions: [ - { - cred_def_id: credDefId, - }, - ], - }, }, requested_predicates: { - [requestKey2]: { + [requestPredicateKey]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -301,61 +306,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({ @@ -413,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), }, @@ -468,42 +451,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: { + age: { + name: 'age', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -511,61 +488,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 new file mode 100644 index 0000000000..407e975271 --- /dev/null +++ b/packages/anoncreds/src/protocols/proofs/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts @@ -0,0 +1,272 @@ +import type { EventReplaySubject } from '../../../../../../core/tests' +import type { AnonCredsTestsAgent } from '../../../../../tests/legacyAnonCredsSetup' + +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: 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 () => { + ;({ + 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() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + 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: aliceConnectionId, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], + }, + }, + }) + + testLogger.test('Faber waits for presentation from Alice') + testLogger.test('Alice waits till it receives presentation ack') + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) + }) + + test("Faber starts with proof requests to Alice, both with autoAcceptProof on 'always'", async () => { + testLogger.test('Faber sends presentation request to Alice') + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v1', + connectionId: faberConnectionId, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + 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, + }, + ], + }, + }, + }, + }, + }) + + testLogger.test('Faber waits for presentation from Alice') + await Promise.all([ + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + ]) + }) + }) + + describe("Auto accept on 'contentApproved'", () => { + beforeAll(async () => { + testLogger.test('Initializing the agents') + ;({ + 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') + await faberAgent.shutdown() + await faberAgent.wallet.delete() + await aliceAgent.shutdown() + await aliceAgent.wallet.delete() + }) + + 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({ + connectionId: aliceConnectionId, + protocolVersion: 'v1', + proofFormats: { + indy: { + name: 'abc', + version: '1.0', + attributes: [ + { + name: 'name', + value: 'John', + credentialDefinitionId, + }, + ], + predicates: [ + { + name: 'age', + predicate: '>=', + threshold: 50, + credentialDefinitionId, + }, + ], + }, + }, + }) + + testLogger.test('Faber waits for presentation proposal from Alice') + const faberProofExchangeRecord = await waitForProofExchangeRecord(faberAgent, { + threadId: aliceProofExchangeRecord.threadId, + state: ProofState.ProposalReceived, + }) + + testLogger.test('Faber accepts presentation proposal from Alice') + await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id }) + + 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 () => { + testLogger.test('Faber sends presentation request to Alice') + + await faberAgent.proofs.requestProof({ + protocolVersion: 'v1', + connectionId: faberConnectionId, + proofFormats: { + indy: { + name: 'proof-request', + version: '1.0', + 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, + }, + ], + }, + }, + }, + }, + }) + + testLogger.test('Alice waits for request from Faber') + const { id: proofRecordId } = await waitForProofExchangeRecord(aliceAgent, { + state: ProofState.RequestReceived, + }) + + const { proofFormats } = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId }) + await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) + + await Promise.all([ + waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), + waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), + ]) + }) + }) +}) 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/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/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/__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/__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/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/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 a140e13cfb..2de326adf2 100644 --- a/packages/anoncreds/src/utils/index.ts +++ b/packages/anoncreds/src/utils/index.ts @@ -1,8 +1,16 @@ 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' export { encodeCredentialValue, checkValidCredentialValueEncoding } from './credential' 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/anoncreds.test.ts b/packages/anoncreds/tests/anoncreds.test.ts index f905c92db9..5d0561d8f0 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' @@ -81,7 +82,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..e5a1082b64 --- /dev/null +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -0,0 +1,438 @@ +import type { EventReplaySubject } from '../../core/tests' +import type { + AnonCredsRegisterCredentialDefinitionOptions, + AnonCredsRequestedAttribute, + AnonCredsRequestedPredicate, + AnonCredsOfferCredentialFormat, + AnonCredsSchema, + RegisterCredentialDefinitionReturnStateFinished, + RegisterSchemaReturnStateFinished, +} from '../src' +import type { AutoAcceptProof, ConnectionRecord } from '@aries-framework/core' + +import { + CacheModule, + InMemoryLruCache, + Agent, + AriesFrameworkError, + AutoAcceptCredential, + CredentialEventTypes, + CredentialsModule, + CredentialState, + ProofEventTypes, + ProofsModule, + ProofState, + V2CredentialProtocol, + V2ProofProtocol, + DidsModule, +} from '@aries-framework/core' +import { randomUUID } from 'crypto' + +import { setupSubjectTransports, setupEventReplaySubjects } from '../../core/tests' +import { + getAgentOptions, + makeConnection, + waitForCredentialRecordSubject, + waitForProofExchangeRecordSubject, +} from '../../core/tests/helpers' +import testLogger from '../../core/tests/logger' +import { + IndySdkAnonCredsRegistry, + IndySdkModule, + IndySdkSovDidRegistrar, + IndySdkSovDidResolver, +} from '../../indy-sdk/src' +import { getIndySdkModuleConfig } from '../../indy-sdk/tests/setupIndySdkModule' +import { + V1CredentialProtocol, + V1ProofProtocol, + AnonCredsModule, + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, +} 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(getIndySdkModuleConfig()), + cache: new CacheModule({ + cache: new InMemoryLruCache({ limit: 100 }), + }), + } as const + + return modules +} + +export async function presentLegacyAnonCredsProof({ + verifierAgent, + verifierReplay, + + holderAgent, + holderReplay, + + verifierHolderConnectionId, + + request: { attributes, predicates }, +}: { + holderAgent: AnonCredsTestsAgent + holderReplay: EventReplaySubject + + verifierAgent: AnonCredsTestsAgent + verifierReplay: EventReplaySubject + + 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', + requested_attributes: attributes, + requested_predicates: 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: EventReplaySubject + + holderAgent: AnonCredsTestsAgent + holderReplay: EventReplaySubject + + 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, + } +} + +interface SetupAnonCredsTestsReturn { + issuerAgent: AnonCredsTestsAgent + issuerReplay: EventReplaySubject + + holderAgent: AnonCredsTestsAgent + 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 ? EventReplaySubject : undefined + + schemaId: string + credentialDefinitionId: string +} + +export async function setupAnonCredsTests< + VerifierName extends string | undefined = undefined, + CreateConnections extends boolean = true +>({ + issuerName, + holderName, + verifierName, + autoAcceptCredentials, + autoAcceptProofs, + attributeNames, + createConnections, +}: { + issuerName: string + holderName: string + verifierName?: VerifierName + autoAcceptCredentials?: AutoAcceptCredential + autoAcceptProofs?: AutoAcceptProof + attributeNames: string[] + createConnections?: CreateConnections +}): Promise> { + const issuerAgent = new Agent( + getAgentOptions( + issuerName, + { + endpoints: ['rxjs:issuer'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + ) + ) + + const holderAgent = new Agent( + getAgentOptions( + holderName, + { + endpoints: ['rxjs:holder'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + ) + ) + + const verifierAgent = verifierName + ? new Agent( + getAgentOptions( + verifierName, + { + endpoints: ['rxjs:verifier'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials, + autoAcceptProofs, + }) + ) + ) + : undefined + + setupSubjectTransports(verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent]) + const [issuerReplay, holderReplay, verifierReplay] = setupEventReplaySubjects( + verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + + await issuerAgent.initialize() + await holderAgent.initialize() + if (verifierAgent) await verifierAgent.initialize() + + const { credentialDefinition, schema } = await prepareForAnonCredsIssuance(issuerAgent, { + 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, + }) + + 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 (verifierAgent) { + ;[holderVerifierConnection, verifierHolderConnection] = await makeConnection(holderAgent, verifierAgent) + } + } + + return { + issuerAgent, + issuerReplay, + + holderAgent, + holderReplay, + + verifierAgent: verifierName ? verifierAgent : undefined, + verifierReplay: verifierName ? verifierReplay : undefined, + + credentialDefinitionId: credentialDefinition.credentialDefinitionId, + schemaId: schema.schemaId, + + issuerHolderConnectionId: issuerHolderConnection?.id, + holderIssuerConnectionId: holderIssuerConnection?.id, + holderVerifierConnectionId: holderVerifierConnection?.id, + verifierHolderConnectionId: verifierHolderConnection?.id, + } as unknown as SetupAnonCredsTestsReturn +} + +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 } = await agent.modules.anoncreds.registerSchema({ + schema, + options: { + didIndyNamespace: 'pool:localtest', + }, + }) + + testLogger.test(`created schema with id ${schemaState.schemaId}`, schema) + + if (schemaState.state !== 'finished') { + throw new AriesFrameworkError( + `Schema not created: ${schemaState.state === 'failed' ? schemaState.reason : 'Not finished'}` + ) + } + + return schemaState +} + +async function registerCredentialDefinition( + agent: AnonCredsTestsAgent, + credentialDefinition: AnonCredsRegisterCredentialDefinitionOptions +): Promise { + const { credentialDefinitionState } = await agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition, + options: { + didIndyNamespace: 'pool:localtest', + }, + }) + + if (credentialDefinitionState.state !== 'finished') { + throw new AriesFrameworkError( + `Credential definition not created: ${ + credentialDefinitionState.state === 'failed' ? credentialDefinitionState.reason : 'Not finished' + }` + ) + } + + return credentialDefinitionState +} 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 baa95e5324..02564c4d0a 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/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/bbs-signatures/tests/bbs-signatures.e2e.test.ts b/packages/bbs-signatures/tests/bbs-signatures.e2e.test.ts index efbc08027a..0ee5b85036 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,7 +17,6 @@ import { LinkedDataProof, W3cPresentation, W3cVerifiablePresentation, - IndyWallet, Ed25519Signature2018, TypedArrayEncoder, } from '@aries-framework/core' @@ -26,6 +25,8 @@ import { SignatureSuiteRegistry } from '../../core/src/modules/vc/SignatureSuite 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' @@ -60,16 +61,15 @@ 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 = TypedArrayEncoder.fromString('testseed000000000000000000000001') const privateKey = TypedArrayEncoder.fromString('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 d428dd65be..e956c633d7 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 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' @@ -25,25 +25,21 @@ const walletConfig: WalletConfig = { } describeSkipNode17And18('BBS Signing Provider', () => { - let indyWallet: IndyWallet + let wallet: Wallet const seed = TypedArrayEncoder.fromString('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 b445f0f24b..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 @@ -1,48 +1,80 @@ -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 { TypedArrayEncoder } from '@aries-framework/core' - -import { InjectionSymbols } from '../../core/src/constants' +import { TypedArrayEncoder } from '../../core/src' 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 = TypedArrayEncoder.fromString('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, privateKey: 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, + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + }) + await faberAgent.context.wallet.createKey({ + keyType: KeyType.Bls12381g2, + seed: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + }) }) + afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -52,45 +84,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, @@ -98,16 +93,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, @@ -115,18 +111,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(faberCredentialRecord.id) expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', '@id': expect.any(String), @@ -164,37 +154,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, }) @@ -203,7 +188,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, }) @@ -216,15 +201,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({ - 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/package.json b/packages/core/package.json index e4295e0d2a..7b3dd8da9b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -30,11 +30,9 @@ "@stablelib/ed25519": "^1.0.2", "@stablelib/random": "^1.0.1", "@stablelib/sha256": "^1.0.1", - "@types/indy-sdk": "1.16.26", "@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", @@ -59,6 +57,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/agent/Agent.ts b/packages/core/src/agent/Agent.ts index 3785d00f4a..88ff3597fb 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' @@ -16,8 +17,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 +78,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,13 +162,10 @@ 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 [, module] of Object.entries(this.dependencyManager.registeredModules) as [string, Module][]) { + if (module.initialize) { + await module.initialize(this.agentContext) + } } for (const transport of this.inboundTransports) { diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index a2df97a94c..09f348652d 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -33,13 +33,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 @@ -50,13 +43,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 { ...agentOptions, modules: { myModule: new MyModule(), + ...getIndySdkModules(), }, }) @@ -79,6 +78,7 @@ describe('Agent', () => { mediationRecipient: new RecipientModule({ maximumMessagePickup: 42, }), + ...getIndySdkModules(), }, }) @@ -175,13 +175,9 @@ describe('Agent', () => { 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) @@ -216,9 +212,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( @@ -248,20 +241,18 @@ 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', - '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(13) }) }) 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/crypto/__tests__/JwsService.test.ts b/packages/core/src/crypto/__tests__/JwsService.test.ts index 4080ab2f24..15dc242f9b 100644 --- a/packages/core/src/crypto/__tests__/JwsService.test.ts +++ b/packages/core/src/crypto/__tests__/JwsService.test.ts @@ -1,10 +1,11 @@ import type { AgentContext } from '../../agent' import type { Key, Wallet } from '@aries-framework/core' +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, TypedArrayEncoder } from '../../utils' -import { IndyWallet } from '../../wallet/IndyWallet' import { JwsService } from '../JwsService' import { KeyType } from '../KeyType' import { SigningProviderRegistry } from '../signing-provider' @@ -20,12 +21,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({ diff --git a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts index 5336162c59..93d3610f29 100644 --- a/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts +++ b/packages/core/src/decorators/signature/SignatureDecoratorUtils.test.ts @@ -1,8 +1,11 @@ +import type { Wallet } from '../../wallet' + +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' import { TypedArrayEncoder } from '../../utils' -import { IndyWallet } from '../../wallet/IndyWallet' import { SignatureDecorator } from './SignatureDecorator' import { signData, unpackAndVerifySignatureDecorator } from './SignatureDecoratorUtils' @@ -40,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/index.ts b/packages/core/src/index.ts index 38f4bc2fa8..3ffd7de1f4 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -31,6 +31,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' @@ -42,6 +43,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' @@ -53,7 +55,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' @@ -63,12 +64,13 @@ 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/' -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' 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..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 @@ -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,21 @@ 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 faberConfig = getAgentOptions( + 'Faber Basic Messages', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) + +const aliceConfig = getAgentOptions( + 'Alice Basic Messages', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) describe('Basic Messages E2E', () => { let faberAgent: Agent 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/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/connections/__tests__/ConnectionService.test.ts b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts index 9cc403ecba..c07c94a893 100644 --- a/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts +++ b/packages/core/src/modules/connections/__tests__/ConnectionService.test.ts @@ -5,6 +5,8 @@ import type { Routing } from '../services/ConnectionService' import { Subject } from 'rxjs' +import { IndySdkWallet } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../../indy-sdk/tests/setupIndySdkModule' 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/connections/__tests__/connection-manual.e2e.test.ts b/packages/core/src/modules/connections/__tests__/connection-manual.e2e.test.ts index dc07e9639f..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 @@ -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' @@ -45,42 +45,38 @@ 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'], + }, + getIndySdkModules() + ) + const bobAgentOptions = getAgentOptions( + 'Manual Connection Flow Bob', + { + label: 'bob', + autoAcceptConnections: false, + endpoints: ['rxjs:bob'], + }, + getIndySdkModules() + ) + const faberAgentOptions = getAgentOptions( + 'Manual Connection Flow Faber', + { + autoAcceptConnections: false, + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() + ) 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 a7d762e248..9cae75eb28 100644 --- a/packages/core/src/modules/credentials/CredentialsModule.ts +++ b/packages/core/src/modules/credentials/CredentialsModule.ts @@ -9,16 +9,14 @@ 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' 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 = [] // CredentialsModuleOptions makes the credentialProtocols property optional from the config, as it will set it when not provided. export type CredentialsModuleOptions = Optional< @@ -39,26 +37,10 @@ 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/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 { ) }) - 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(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/__tests__/fixtures.ts b/packages/core/src/modules/credentials/__tests__/fixtures.ts index b8e5e7451c..057ae8d4e1 100644 --- a/packages/core/src/modules/credentials/__tests__/fixtures.ts +++ b/packages/core/src/modules/credentials/__tests__/fixtures.ts @@ -1,35 +1,3 @@ -import type { Schema } from 'indy-sdk' - -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 credOffer = { - schema_id: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - cred_def_id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:49:TAG', - key_correctness_proof: { - c: '50047550092211803100898435599448498249230644214602846259465380105187911562981', - xz_cap: - '903377919969858361861015636539761203188657065139923565169527138921408162179186528356880386741834936511828233627399006489728775544195659624738894378139967421189010372215352983118513580084886680005590351907106638703178655817619548698392274394080197104513101326422946899502782963819178061725651195158952405559244837834363357514238035344644245428381747318500206935512140018411279271654056625228252895211750431161165113594675112781707690650346028518711572046490157895995321932792559036799731075010805676081761818738662133557673397343395090042309895292970880031625026873886199268438633391631171327618951514526941153292890331525143330509967786605076984412387036942171388655140446222693051734534012842', - xr_cap: [[], [], []], - }, - nonce: '947121108704767252195123', -} - export const credReq = { prover_did: 'did:sov:Y8iyDrCHfUpBY2jkd7Utfx', cred_def_id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:51:TAG', @@ -51,12 +19,3 @@ export const credReq = { }, nonce: '784158051402761459123237', } - -export const schema: Schema = { - name: 'schema', - attrNames: ['name', 'age'], - id: 'TL1EaPFCZ8Si5aUrqScBDt:2:test-schema-1599055118161:1.0', - seqNo: 989798923653, - ver: '1.0', - version: '1.0', -} 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__/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 e62124e6f2..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/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 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/index.ts b/packages/core/src/modules/credentials/protocol/index.ts index 6433655022..cb3d5c3b51 100644 --- a/packages/core/src/modules/credentials/protocol/index.ts +++ b/packages/core/src/modules/credentials/protocol/index.ts @@ -1,3 +1,11 @@ -export * from './v1' export * from './v2' 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/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts b/packages/core/src/modules/credentials/protocol/revocation-notification/services/RevocationNotificationService.ts index f3636c689a..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 @@ -41,12 +41,13 @@ export class RevocationNotificationService { private async processRevocationNotification( agentContext: AgentContext, - indyRevocationRegistryId: string, - indyCredentialRevocationId: string, + anonCredsRevocationRegistryId: string, + anonCredsCredentialRevocationId: string, connection: ConnectionRecord, comment?: string ) { - const query = { indyRevocationRegistryId, indyCredentialRevocationId, connectionId: connection.id } + // TODO: can we extract support for this revocation notification handler to the anoncreds module? + const query = { anonCredsRevocationRegistryId, anonCredsCredentialRevocationId, connectionId: connection.id } this.logger.trace(`Getting record by query for revocation notification:`, query) const credentialRecord = await this.credentialRepository.getSingleByQuery(agentContext, query) @@ -88,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 ) @@ -131,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 c0b3e57904..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' @@ -9,7 +10,6 @@ import { Dispatcher } from '../../../../../../agent/Dispatcher' import { EventEmitter } from '../../../../../../agent/EventEmitter' import { DidExchangeState } from '../../../../../connections' import { CredentialEventTypes } from '../../../../CredentialEvents' -import { CredentialMetadataKeys } from '../../../../repository' import { CredentialRepository } from '../../../../repository/CredentialRepository' import { V1RevocationNotificationMessage, V2RevocationNotificationMessage } from '../../messages' import { RevocationNotificationService } from '../RevocationNotificationService' @@ -32,9 +32,7 @@ describe('RevocationNotificationService', () => { let eventEmitter: EventEmitter beforeEach(() => { - const agentConfig = getAgentConfig('RevocationNotificationService', { - indyLedgers: [], - }) + const agentConfig = getAgentConfig('RevocationNotificationService') agentContext = getAgentContext() @@ -72,16 +70,19 @@ describe('RevocationNotificationService', () => { state: CredentialState.Done, }) - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - indyRevocationRegistryId: + const metadata = { + revocationRegistryId: 'AsB27X6KRrJFsqZ3unNAH6:4:AsB27X6KRrJFsqZ3unNAH6:3:cl:48187:default:CL_ACCUM:3b24a9b0-a979-41e0-9964-2292f2b1b7e9', - indyCredentialRevocationId: '1', - }) + credentialRevocationId: '1', + } satisfies AnonCredsCredentialMetadata + + // Set required tags + credentialRecord.setTag('anonCredsRevocationRegistryId', metadata.revocationRegistryId) + credentialRecord.setTag('anonCredsCredentialRevocationId', metadata.credentialRevocationId) mockFunction(credentialRepository.getSingleByQuery).mockResolvedValueOnce(credentialRecord) - const { indyRevocationRegistryId, indyCredentialRevocationId } = credentialRecord.getTags() - const revocationNotificationThreadId = `indy::${indyRevocationRegistryId}::${indyCredentialRevocationId}` + const revocationNotificationThreadId = `indy::${metadata.revocationRegistryId}::${metadata.credentialRevocationId}` const revocationNotificationMessage = new V1RevocationNotificationMessage({ issueThread: revocationNotificationThreadId, comment: 'Credential has been revoked', @@ -182,15 +183,14 @@ describe('RevocationNotificationService', () => { state: CredentialState.Done, }) - credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, { - indyRevocationRegistryId: + const metadata = { + revocationRegistryId: 'AsB27X6KRrJFsqZ3unNAH6:4:AsB27X6KRrJFsqZ3unNAH6:3:cl:48187:default:CL_ACCUM:3b24a9b0-a979-41e0-9964-2292f2b1b7e9', - indyCredentialRevocationId: '1', - }) + credentialRevocationId: '1', + } satisfies AnonCredsCredentialMetadata mockFunction(credentialRepository.getSingleByQuery).mockResolvedValueOnce(credentialRecord) - const { indyRevocationRegistryId, indyCredentialRevocationId } = credentialRecord.getTags() - const revocationNotificationCredentialId = `${indyRevocationRegistryId}::${indyCredentialRevocationId}` + const revocationNotificationCredentialId = `${metadata.revocationRegistryId}::${metadata.credentialRevocationId}` const revocationNotificationMessage = new V2RevocationNotificationMessage({ credentialId: revocationNotificationCredentialId, diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts index df39187210..901834a106 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' @@ -699,7 +698,7 @@ export class V2CredentialProtocol -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,13 +195,13 @@ const mockCredentialRecord = ({ } = {}) => { const credentialRecord = new CredentialExchangeRecord({ id, - credentialAttributes: credentialAttributes || credentialPreview.attributes, + credentialAttributes: credentialAttributes, state: state || CredentialState.OfferSent, threadId: threadId || 'thread-id', connectionId: connectionId ?? '123', credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'test', credentialRecordId: '123456', }, ], @@ -230,25 +209,37 @@ 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(), + processCredential: jest.fn(), + acceptOffer: () => ({ attachment: requestAttachment, format: requestFormat }), + processRequest: jest.fn(), +} as unknown as TestCredentialFormatService + describe('credentialProtocol', () => { let credentialProtocol: V2CredentialProtocol @@ -264,7 +255,7 @@ describe('credentialProtocol', () => { ]) credentialProtocol = new V2CredentialProtocol({ - credentialFormats: [indyCredentialFormatService, jsonLdCredentialFormatService], + credentialFormats: [testCredentialFormatService], }) }) @@ -273,28 +264,17 @@ 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', 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 +297,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 +331,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 +353,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 +383,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,12 +403,6 @@ describe('credentialProtocol', () => { describe('acceptRequest', () => { test(`updates state to ${CredentialState.CredentialIssued}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -462,12 +424,6 @@ describe('credentialProtocol', () => { }) test(`emits stateChange event from ${CredentialState.RequestReceived} to ${CredentialState.CredentialIssued}`, async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -501,12 +457,6 @@ describe('credentialProtocol', () => { }) test('returns credential response message base on credential request message', async () => { - mockFunction(indyCredentialFormatService.supportsFormat).mockReturnValue(true) - mockFunction(indyCredentialFormatService.acceptRequest).mockResolvedValue({ - attachment: credentialAttachment, - format: credentialFormat, - }) - const credentialRecord = mockCredentialRecord({ state: CredentialState.RequestReceived, connectionId: 'b1e2f039-aa39-40be-8643-6ce2797b5190', @@ -539,8 +489,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, }) @@ -553,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) }) }) @@ -808,8 +753,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 +771,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 +786,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 +800,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..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,4 +1,7 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +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 +15,70 @@ 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, + previewAttributes: [ + { + mimeType: 'text/plain', + name: 'name', + value: 'John', + }, + { + mimeType: 'text/plain', + name: 'age', + value: '99', + }, + ], + }), + 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') -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 +86,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 +94,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 +105,6 @@ const agentContext = getAgentContext({ [CredentialRepository, credentialRepository], [DidCommMessageRepository, didCommMessageRepository], [RoutingService, routingService], - [IndyLedgerService, indyLedgerService], [Dispatcher, dispatcher], [ConnectionService, connectionService], [EventEmitter, eventEmitter], @@ -83,35 +117,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 +134,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 +161,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 +181,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 +212,9 @@ describe('V2CredentialProtocolOffer', () => { const credentialOfferMessage = new V2OfferCredentialMessage({ formats: [offerFormat], comment: 'some comment', - credentialPreview, + credentialPreview: new V2CredentialPreview({ + attributes: [], + }), offerAttachments: [offerAttachment], }) @@ -230,8 +224,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 +243,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 0c62263961..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 @@ -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' @@ -6,7 +7,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 { + 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' @@ -15,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', @@ -29,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 @@ -53,8 +66,11 @@ describe('V2 Connectionless Credentials', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIssuance(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() @@ -144,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), }, ], @@ -165,7 +181,7 @@ describe('V2 Connectionless Credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { + '_anonCreds/anonCredsCredential': { credentialDefinitionId, }, }, @@ -225,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 0bd25c345e..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,23 +1,24 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { AcceptCredentialOfferOptions, AcceptCredentialProposalOptions } from '../../../CredentialsApiOptions' -import type { Schema } from 'indy-sdk' +import type { AnonCredsTestsAgent } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import type { EventReplaySubject } from '../../../../../../tests' -import { setupCredentialTests, waitForCredentialRecord } from '../../../../../../tests/helpers' +import { setupAnonCredsTests } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +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' import { V2CredentialPreview } from '../messages/V2CredentialPreview' -describe('v2 credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let schema: Schema - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord +describe('V2 Credentials Auto Accept', () => { + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let credentialDefinitionId: string + let schemaId: string + let faberConnectionId: string + let aliceConnectionId: string + const credentialPreview = V2CredentialPreview.fromRecord({ name: 'John', age: '99', @@ -31,13 +32,23 @@ 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, + 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, + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) }) afterAll(async () => { @@ -47,35 +58,31 @@ describe('v2 credentials', () => { await aliceAgent.wallet.delete() }) - 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, - }) - + test("Alice starts with V2 credential proposal to Faber, both with autoAcceptCredential on 'always'", async () => { testLogger.test('Alice sends credential proposal to Faber') - await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, comment: 'v2 propose credential test', }) 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, @@ -83,9 +90,9 @@ describe('v2 credentials', () => { createdAt: expect.any(Date), metadata: { data: { - '_internal/indyCredential': { - schemaId: schema.id, - credentialDefinitionId: credDefId, + '_anonCreds/anonCredsCredential': { + schemaId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -93,48 +100,42 @@ describe('v2 credentials', () => { }) }) - 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, - }) - + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'always'", async () => { 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: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, 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: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, credentials: [ { - credentialRecordType: 'indy', + credentialRecordType: 'anoncreds', credentialRecordId: expect.any(String), }, ], @@ -142,7 +143,10 @@ describe('v2 credentials', () => { }) 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), @@ -152,13 +156,24 @@ 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, + 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.ContentApproved, + attributeNames: ['name', 'age', 'x-ray', 'profile_picture'], + })) }) afterAll(async () => { @@ -168,90 +183,80 @@ 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 - - testLogger.test('Faber starts listening for credential proposal from Alice') - const faberPropReceivedPromise = waitForCredentialRecord(faberAgent, { - state: CredentialState.ProposalReceived, - }) - await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }) 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: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, 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: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, 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: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, @@ -259,86 +264,77 @@ describe('v2 credentials', () => { }) }) - 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, - }) - + test("Faber starts with V2 credential offer to Alice, both with autoAcceptCredential on 'contentApproved'", async () => { 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: faberConnection.id, + connectionId: faberConnectionId, credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, protocolVersion: 'v2', }) 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, - connectionId: aliceConnection.id, + 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: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, }, 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), @@ -346,122 +342,99 @@ describe('v2 credentials', () => { }) }) - 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, - }) - + 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 aliceCredProposal = await aliceAgent.credentials.proposeCredential({ - connectionId: aliceConnection.id, + let aliceCredentialRecord = await aliceAgent.credentials.proposeCredential({ + connectionId: aliceConnectionId, protocolVersion: 'v2', credentialFormats: { indy: { attributes: credentialPreview.attributes, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, 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: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, }) 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, - connectionId: aliceConnection.id, + 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, - }) - + 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') - await faberAgent.credentials.offerCredential({ + const 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') - 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, - connectionId: aliceConnection.id, + 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, - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, }, }, 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-credentials.e2e.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2-credentials.e2e.test.ts index c3b73fd6ef..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,27 +1,25 @@ -import type { Agent } from '../../../../../agent/Agent' -import type { ConnectionRecord } from '../../../../connections' -import type { CredentialStateChangedEvent } from '../../../CredentialEvents' -import type { IndyCredPropose } from '../../../formats/indy/models/IndyCredPropose' -import type { ReplaySubject } from 'rxjs' +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 { - issueCredential, - 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' -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' const credentialPreview = V2CredentialPreview.fromRecord({ @@ -32,17 +30,16 @@ const credentialPreview = V2CredentialPreview.fromRecord({ }) describe('v2 credentials', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let faberConnection: ConnectionRecord - let aliceConnection: ConnectionRecord - let aliceCredentialRecord: CredentialExchangeRecord - let faberCredentialRecord: CredentialExchangeRecord - let faberReplay: ReplaySubject - let aliceReplay: ReplaySubject - - let credPropose: IndyCredPropose + 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', @@ -52,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', @@ -76,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: { @@ -93,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, }) @@ -111,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, }) @@ -180,7 +188,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, protocolVersion: 'v2', state: CredentialState.RequestSent, threadId: expect.any(String), @@ -216,34 +224,36 @@ describe('v2 credentials', () => { }) test('Faber issues credential which is then deleted from Alice`s wallet', async () => { - const { holderCredential } = await issueCredential({ + 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, }, }) // 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.` ) }) @@ -256,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, }, }, @@ -280,7 +290,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -306,7 +316,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -326,7 +336,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -341,7 +351,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, state: CredentialState.RequestSent, protocolVersion: 'v2', threadId: aliceCredentialExchangeRecord.threadId, @@ -391,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, @@ -413,7 +423,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -432,7 +442,7 @@ describe('v2 credentials', () => { credentialRecordId: faberCredentialRecord.id, credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: newCredentialPreview.attributes, }, }, @@ -451,7 +461,7 @@ describe('v2 credentials', () => { credentialRecordId: aliceCredentialRecord.id, credentialFormats: { indy: { - ...credPropose, + ...indyCredentialProposal, attributes: newCredentialPreview.attributes, }, }, @@ -473,7 +483,7 @@ describe('v2 credentials', () => { comment: 'V2 Indy Proposal', credentialFormats: { indy: { - credentialDefinitionId: credDefId, + credentialDefinitionId: credentialDefinitionId, attributes: credentialPreview.attributes, }, }, @@ -492,7 +502,7 @@ describe('v2 credentials', () => { }) expect(offerCredentialExchangeRecord).toMatchObject({ - connectionId: aliceConnection.id, + connectionId: aliceConnectionId, state: CredentialState.RequestSent, protocolVersion: 'v2', }) @@ -630,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 ea148db552..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 @@ -1,70 +1,16 @@ -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, prepareForIssuance, 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 { TypedArrayEncoder } from '../../../../../utils' -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 privateKey = TypedArrayEncoder.fromString('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', @@ -75,47 +21,35 @@ 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 prepareForIssuance(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({ privateKey, 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({ + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + keyType: KeyType.Ed25519, + }) }) afterEach(async () => { @@ -137,36 +71,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 d8a3521a27..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 @@ -1,11 +1,8 @@ -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 { TypedArrayEncoder } from '../../../../../utils' @@ -13,47 +10,50 @@ 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 privateKey = TypedArrayEncoder.fromString('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({ privateKey, 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({ + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + keyType: KeyType.Ed25519, + }) }) + afterAll(async () => { await faberAgent.shutdown() await faberAgent.wallet.delete() @@ -61,11 +61,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, @@ -75,7 +75,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, }) @@ -93,19 +93,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, }) @@ -136,22 +136,23 @@ 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({ privateKey, 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({ + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + keyType: KeyType.Ed25519, + }) }) afterAll(async () => { @@ -161,10 +162,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, @@ -185,7 +186,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, }) @@ -213,12 +214,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, }, @@ -226,7 +227,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, }) @@ -236,7 +237,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) @@ -279,19 +280,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, }) @@ -301,7 +302,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) @@ -340,10 +341,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, @@ -386,7 +387,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 98621d7a40..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 @@ -1,31 +1,52 @@ -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 { + LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, + V1CredentialProtocol, + V1ProofProtocol, + AnonCredsModule, +} from '../../../../../../../anoncreds/src' +import { prepareForAnonCredsIssuance } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { + IndySdkAnonCredsRegistry, + IndySdkModule, + IndySdkSovDidRegistrar, + IndySdkSovDidResolver, +} from '../../../../../../../indy-sdk/src' +import { indySdk } from '../../../../../../../indy-sdk/tests/setupIndySdkModule' +import { + setupEventReplaySubjects, + setupSubjectTransports, + genesisPath, + taaAcceptanceMechanism, + taaVersion, + getAgentOptions, + waitForCredentialRecordSubject, + testLogger, + makeConnection, +} from '../../../../../../tests' +import { Agent } from '../../../../../agent/Agent' import { KeyType } from '../../../../../crypto' -import { DidCommMessageRepository } from '../../../../../storage' import { TypedArrayEncoder } from '../../../../../utils' import { JsonTransformer } from '../../../../../utils/JsonTransformer' +import { CacheModule, InMemoryLruCache } from '../../../../cache' +import { DidsModule, KeyDidRegistrar, KeyDidResolver } 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', @@ -53,28 +74,110 @@ describe('credentials', () => { birthCountry: 'Bahamas', birthDate: '1958-07-17', }, - } - - let signCredentialOptions: JsonLdCredentialDetailFormat - - let wallet - const privateKey = TypedArrayEncoder.fromString('testseed000000000000000000000001') - let credDefId: string + }, + options: { + proofType: 'Ed25519Signature2018', + proofPurpose: 'assertionMethod', + }, +} + +const indyCredentialFormat = new LegacyIndyCredentialFormatService() +const jsonLdCredentialFormat = new JsonLdCredentialFormatService() +const indyProofFormat = new LegacyIndyProofFormatService() + +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 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({ privateKey, keyType: KeyType.Ed25519 }) - signCredentialOptions = { - credential: inputDocAsJson, - options: { - proofType: 'Ed25519Signature2018', - proofPurpose: 'assertionMethod', - }, - } + faberAgent = new Agent( + getAgentOptions( + 'Faber Agent Indy/JsonLD', + { + endpoints: ['rxjs:faber'], + }, + getIndyJsonLdModules() + ) + ) + aliceAgent = new Agent( + getAgentOptions( + 'Alice Agent Indy/JsonLD', + { + endpoints: ['rxjs:alice'], + }, + getIndyJsonLdModules() + ) + ) + + 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'], + issuerId: faberAgent.publicDid?.did as string, + }) + credentialDefinitionId = credentialDefinition.credentialDefinitionId + + await faberAgent.context.wallet.createKey({ + privateKey: TypedArrayEncoder.fromString('testseed000000000000000000000001'), + keyType: KeyType.Ed25519, + }) }) afterAll(async () => { @@ -87,8 +190,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, @@ -96,13 +199,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, }) @@ -114,18 +217,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 aliceAgent.credentials.findOfferMessage(aliceCredentialRecord.id) expect(JsonTransformer.toJSON(offerMessage)).toMatchObject({ '@type': 'https://didcomm.org/issue-credential/2.0/offer-credential', '@id': expect.any(String), @@ -163,93 +260,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 () => { @@ -261,35 +352,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, }) @@ -301,7 +391,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 @@ -309,20 +399,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', @@ -408,139 +492,132 @@ 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({ + '@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/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/credentials/repository/CredentialExchangeRecord.ts b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts index 290865b83a..c9af8da909 100644 --- a/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts +++ b/packages/core/src/modules/credentials/repository/CredentialExchangeRecord.ts @@ -1,4 +1,3 @@ -import type { CredentialMetadata } from './CredentialMetadataTypes' import type { TagsBase } from '../../../storage/BaseRecord' import type { AutoAcceptCredential } from '../models/CredentialAutoAcceptType' import type { CredentialState } from '../models/CredentialState' @@ -10,11 +9,8 @@ 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' - export interface CredentialExchangeRecordProps { id?: string createdAt?: Date @@ -38,8 +34,6 @@ export type DefaultCredentialTags = { connectionId?: string state: CredentialState credentialIds: string[] - indyRevocationRegistryId?: string - indyCredentialRevocationId?: string } export interface CredentialRecordBinding { @@ -47,11 +41,7 @@ export interface CredentialRecordBinding { credentialRecordId: string } -export class CredentialExchangeRecord extends BaseRecord< - DefaultCredentialTags, - CustomCredentialTags, - CredentialMetadata -> { +export class CredentialExchangeRecord extends BaseRecord { public connectionId?: string public threadId!: string public state!: CredentialState @@ -92,7 +82,6 @@ export class CredentialExchangeRecord extends BaseRecord< } public getTags() { - const metadata = this.metadata.get(CredentialMetadataKeys.IndyCredential) const ids = this.credentials.map((c) => c.credentialRecordId) return { @@ -101,29 +90,9 @@ export class CredentialExchangeRecord extends BaseRecord< connectionId: this.connectionId, state: this.state, credentialIds: ids, - indyRevocationRegistryId: metadata?.indyRevocationRegistryId, - indyCredentialRevocationId: metadata?.indyCredentialRevocationId, } } - 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/CredentialMetadataTypes.ts b/packages/core/src/modules/credentials/repository/CredentialMetadataTypes.ts deleted file mode 100644 index 7c645333cb..0000000000 --- a/packages/core/src/modules/credentials/repository/CredentialMetadataTypes.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { CredReqMetadata } from 'indy-sdk' - -export enum CredentialMetadataKeys { - IndyCredential = '_internal/indyCredential', - IndyRequest = '_internal/indyRequest', -} - -export type CredentialMetadata = { - [CredentialMetadataKeys.IndyCredential]: { - schemaId?: string - credentialDefinitionId?: string - indyRevocationRegistryId?: string - indyCredentialRevocationId?: string - } - [CredentialMetadataKeys.IndyRequest]: CredReqMetadata -} 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/credentials/repository/index.ts b/packages/core/src/modules/credentials/repository/index.ts index b7b986ad3e..980f320cfd 100644 --- a/packages/core/src/modules/credentials/repository/index.ts +++ b/packages/core/src/modules/credentials/repository/index.ts @@ -1,3 +1,2 @@ export * from './CredentialExchangeRecord' export * from './CredentialRepository' -export * from './CredentialMetadataTypes' 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/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__/dids-registrar.e2e.test.ts b/packages/core/src/modules/dids/__tests__/dids-registrar.e2e.test.ts index ccd60edf71..70aa731310 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,24 @@ 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 { IndySdkModule } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../tests' +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, TypedArrayEncoder } from '@aries-framework/core' + +const agentOptions = getAgentOptions( + 'Faber Dids Registrar', + {}, + { + indySdk: new IndySdkModule({ + indySdk, + }), + } +) describe('dids', () => { let agent: Agent @@ -164,108 +157,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 privateKey = TypedArrayEncoder.fromString( - Array(32 + 1) - .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) - .slice(0, 32) - ) - - const publicKeyEd25519 = generateKeyPairFromSeed(privateKey).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: { - privateKey, - }, - }) - - 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: { - privateKey: privateKey.toString(), - }, - }, - }) - }) }) 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..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,16 +1,23 @@ -import type { SovDidCreateOptions } from '../methods' - +import { IndySdkModule } from '../../../../../indy-sdk/src' +import { indySdk } from '../../../../tests' 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 +const agent = new Agent( + getAgentOptions( + 'Faber Dids', + {}, + { + indySdk: new IndySdkModule({ + indySdk, + }), + } + ) +) +describe('dids', () => { beforeAll(async () => { - agent = new Agent(getAgentOptions('Faber Dids')) await agent.initialize() }) @@ -19,64 +26,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/__tests__/peer-did.test.ts b/packages/core/src/modules/dids/__tests__/peer-did.test.ts index c352fc0383..7ec76f20cb 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 { 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' import { Key, KeyType } from '../../../crypto' import { SigningProviderRegistry } from '../../../crypto/signing-provider' -import { IndyStorageService } from '../../../storage/IndyStorageService' import { JsonTransformer, TypedArrayEncoder } 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/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 21781642ab..0000000000 --- a/packages/core/src/modules/dids/methods/sov/IndySdkSovDidRegistrar.ts +++ /dev/null @@ -1,247 +0,0 @@ -import type { AgentContext } from '../../../../agent' -import type { Buffer } from '../../../../utils' -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 privateKey = options.secret?.privateKey - - if (privateKey && (typeof privateKey !== 'object' || privateKey.length !== 32)) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid private key 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: privateKey?.toString(), - }) - - 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 - privateKey: options.secret?.privateKey?.toString(), - }, - }, - } - } 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?: { - privateKey?: Buffer - } -} - -// 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 7837772932..0000000000 --- a/packages/core/src/modules/dids/methods/sov/__tests__/IndySdkSovDidRegistrar.test.ts +++ /dev/null @@ -1,374 +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 { TypedArrayEncoder } from '../../../../../utils' -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 private key is provided', async () => { - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - - options: { - submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - alias: 'Hello', - }, - secret: { - privateKey: TypedArrayEncoder.fromString('invalid'), - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid private key 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: { - privateKey: TypedArrayEncoder.fromString('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 privateKey = TypedArrayEncoder.fromString('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: { - privateKey, - }, - }) - - 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: { - privateKey: privateKey.toString(), - }, - }, - }) - }) - - it('should correctly create a did:sov document with services', async () => { - const privateKey = TypedArrayEncoder.fromString('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: { - privateKey, - }, - }) - - 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: { - privateKey: privateKey.toString(), - }, - }, - }) - }) - - it('should store the did document', async () => { - const privateKey = TypedArrayEncoder.fromString('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: { - privateKey, - }, - }) - - 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/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..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 @@ -1,47 +1,47 @@ -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 faberAgentOptions = getAgentOptions( + 'Faber Discover Features V1 E2E', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) + +const aliceAgentOptions = getAgentOptions( + 'Alice Discover Features V1 E2E', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) + 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 +53,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 +67,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 +82,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..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 @@ -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,22 @@ import { DiscoverFeaturesEventTypes } from '../DiscoverFeaturesEvents' import { waitForDisclosureSubject, waitForQuerySubject } from './helpers' +const faberAgentOptions = getAgentOptions( + 'Faber Discover Features V2 E2E', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) + +const aliceAgentOptions = getAgentOptions( + 'Alice Discover Features V2 E2E', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) + describe('v2 discover features', () => { let faberAgent: Agent let aliceAgent: Agent @@ -23,27 +38,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 +69,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 +84,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 +219,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/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 44b9713352..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.cachePath}/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 d8abffc113..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.tempPath + `/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/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/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/ProofsModule.ts b/packages/core/src/modules/proofs/ProofsModule.ts index a3d3cf3b4d..b154e6bef3 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 { V1ProofProtocol, V2ProofProtocol } from './protocol' +import { V2ProofProtocol } 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 = [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,10 @@ 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/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 c2012ed566..1126aeda52 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' @@ -34,10 +33,10 @@ 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(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/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 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/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 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/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/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/v1/__tests__/indy-proof-proposal.test.e2e.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.e2e.ts deleted file mode 100644 index f69048fece..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/indy-proof-proposal.test.e2e.ts +++ /dev/null @@ -1,103 +0,0 @@ -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 { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage' -import { ProofState } from '../../../models/ProofState' -import { V1ProposePresentationMessage } from '../messages' - -describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let aliceConnection: ConnectionRecord - let presentationPreview: V1PresentationPreview - let faberProofExchangeRecord: ProofExchangeRecord - let didCommMessageRepository: DidCommMessageRepository - - beforeAll(async () => { - testLogger.test('Initializing the agents') - ;({ faberAgent, aliceAgent, aliceConnection, presentationPreview } = await setupProofsTest( - 'Faber agent', - 'Alice agent' - )) - }) - - 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 faberProofExchangeRecordPromise = waitForProofExchangeRecord(faberAgent, { - state: ProofState.ProposalReceived, - }) - - await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'ProofRequest', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - 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, - }) - - expect(proposal).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', - attributes: [ - { - name: 'name', - credentialDefinitionId: presentationPreview.attributes[0].credentialDefinitionId, - value: 'John', - referent: '0', - }, - { - name: 'image_0', - credentialDefinitionId: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - predicates: [ - { - name: 'age', - credentialDefinitionId: presentationPreview.predicates[0].credentialDefinitionId, - predicate: '>=', - threshold: 50, - }, - ], - }, - }) - - expect(faberProofExchangeRecord).toMatchObject({ - id: expect.anything(), - threadId: faberProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - protocolVersion: 'v1', - }) - }) -}) 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 deleted file mode 100644 index fcfaaaebf1..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-connectionless-proofs.e2e.test.ts +++ /dev/null @@ -1,379 +0,0 @@ -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 { - setupProofsTest, - waitForProofExchangeRecordSubject, - 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[] - - afterEach(async () => { - for (const agent of agents) { - await agent.shutdown() - await agent.wallet.delete() - } - }) - - 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 predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - let aliceProofExchangeRecordPromise = waitForProofExchangeRecordSubject(aliceReplay, { - state: ProofState.RequestReceived, - }) - - // eslint-disable-next-line prefer-const - let { proofRecord: faberProofExchangeRecord, message } = await faberAgent.proofs.createRequest({ - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - testLogger.test('Alice waits for presentation request from Faber') - let aliceProofExchangeRecord = await aliceProofExchangeRecordPromise - - 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 - - // 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 - }) - - 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 predicates = { - age: new ProofPredicateInfo({ - name: 'age', - predicateType: PredicateType.GreaterThanOrEqualTo, - predicateValue: 50, - restrictions: [ - new AttributeFilter({ - credentialDefinitionId: credDefId, - }), - ], - }), - } - - 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({ - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - autoAcceptProof: AutoAcceptProof.ContentApproved, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - await aliceProofExchangeRecordPromise - - await faberProofExchangeRecordPromise - }) - - test('Faber starts with connection-less proof requests to Alice with auto-accept enabled and both agents having a mediator', async () => { - testLogger.test('Faber sends presentation request to Alice') - - const credentialPreview = V1CredentialPreview.fromRecord({ - name: 'John', - age: '99', - }) - - const unique = uuid().substring(0, 4) - - const mediatorAgentOptions = getAgentOptions(`Connectionless proofs with mediator Mediator-${unique}`, { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], - }) - - const faberMessages = new Subject() - const aliceMessages = new Subject() - const mediatorMessages = new Subject() - - const subjectMap = { - 'rxjs:mediator': mediatorMessages, - } - - // Initialize mediator - const mediatorAgent = new Agent(mediatorAgentOptions) - mediatorAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) - mediatorAgent.registerInboundTransport(new SubjectInboundTransport(mediatorMessages)) - await mediatorAgent.initialize() - - const faberMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ - label: 'faber invitation', - handshakeProtocols: [HandshakeProtocol.Connections], - }) - - const aliceMediationOutOfBandRecord = await mediatorAgent.oob.createInvitation({ - label: 'alice invitation', - 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 aliceAgentOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) - - 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() - - agents = [aliceAgent, faberAgent, mediatorAgent] - - const { definition } = await prepareForIssuance(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({ - 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) - - 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({ - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'test-proof-request', - version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - autoAcceptProof: AutoAcceptProof.ContentApproved, - }) - - const { message: requestMessage } = await faberAgent.oob.createLegacyConnectionlessInvitation({ - recordId: faberProofExchangeRecord.id, - message, - domain: 'https://a-domain.com', - }) - - const mediationRecord = await faberAgent.mediationRecipient.findDefaultMediator() - if (!mediationRecord) { - throw new Error('Faber agent has no default mediator') - } - - expect(requestMessage).toMatchObject({ - service: { - recipientKeys: [expect.any(String)], - routingKeys: mediationRecord.routingKeys, - serviceEndpoint: mediationRecord.endpoint, - }, - }) - - await aliceAgent.receiveMessage(requestMessage.toJSON()) - - await aliceProofExchangeRecordPromise - - await faberProofExchangeRecordPromise - - await aliceAgent.mediationRecipient.stopMessagePickup() - await faberAgent.mediationRecipient.stopMessagePickup() - }) -}) diff --git a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts b/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts deleted file mode 100644 index c8a116e8ed..0000000000 --- a/packages/core/src/modules/proofs/protocol/v1/__tests__/v1-proofs-auto-accept.e2e.test.ts +++ /dev/null @@ -1,205 +0,0 @@ -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 { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' -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'", () => { - beforeAll(async () => { - ;({ faberAgent, aliceAgent, credDefId, faberConnection, aliceConnection, presentationPreview } = - await setupProofsTest( - 'Faber Auto Accept Always Proofs', - 'Alice Auto Accept Always Proofs', - AutoAcceptProof.Always - )) - }) - afterAll(async () => { - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - 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, - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'abc', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - }) - - testLogger.test('Faber waits for presentation from Alice') - testLogger.test('Alice waits till it receives presentation ack') - await Promise.all([ - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - ]) - }) - - 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, - proofFormats: { - indy: { - name: 'proof-request', - version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - testLogger.test('Faber waits for presentation from Alice') - await Promise.all([ - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - ]) - }) - }) - - 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 - )) - }) - afterAll(async () => { - testLogger.test('Shutting down both agents') - await faberAgent.shutdown() - await faberAgent.wallet.delete() - await aliceAgent.shutdown() - await aliceAgent.wallet.delete() - }) - - 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({ - connectionId: aliceConnection.id, - protocolVersion: 'v1', - proofFormats: { - indy: { - name: 'abc', - version: '1.0', - attributes: presentationPreview.attributes, - predicates: presentationPreview.predicates, - }, - }, - }) - - testLogger.test('Faber waits for presentation proposal from Alice') - const faberProofExchangeRecord = await waitForProofExchangeRecord(faberAgent, { - threadId: aliceProofExchangeRecord.threadId, - state: ProofState.ProposalReceived, - }) - - testLogger.test('Faber accepts presentation proposal from Alice') - await faberAgent.proofs.acceptProposal({ proofRecordId: faberProofExchangeRecord.id }) - - 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 () => { - 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, - proofFormats: { - indy: { - name: 'proof-request', - version: '1.0', - requestedAttributes: attributes, - requestedPredicates: predicates, - }, - }, - }) - - testLogger.test('Alice waits for request from Faber') - const { id: proofRecordId } = await waitForProofExchangeRecord(aliceAgent, { - state: ProofState.RequestReceived, - }) - - const { proofFormats } = await aliceAgent.proofs.selectCredentialsForRequest({ proofRecordId }) - await aliceAgent.proofs.acceptRequest({ proofRecordId, proofFormats }) - - await Promise.all([ - waitForProofExchangeRecord(aliceAgent, { state: ProofState.Done }), - waitForProofExchangeRecord(faberAgent, { state: ProofState.Done }), - ]) - }) - }) -}) 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..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 @@ -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,14 @@ 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 = { + supportsFormat: () => true, + processRequest: jest.fn(), +} as unknown as ProofFormatService const agentConfig = getAgentConfig('V2ProofProtocolTest') const eventEmitter = new EventEmitter(agentConfig.agentDependencies, new Subject()) @@ -49,7 +51,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 f924869f3f..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 @@ -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, - prepareForIssuance, makeConnection, - issueCredential, -} 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, + 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, + }, + ], + }, + }, }, }, }) @@ -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, + 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, @@ -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 () => { @@ -209,18 +252,19 @@ describe('Present Proof', () => { const unique = uuid().substring(0, 4) - const mediatorOptions = getAgentOptions(`Connectionless proofs with mediator Mediator-${unique}`, { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], - }) + const mediatorOptions = getAgentOptions( + `Connectionless proofs with mediator Mediator-${unique}`, + { + autoAcceptMediationRequests: true, + endpoints: ['rxjs:mediator'], + }, + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) + ) - 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 +282,62 @@ describe('Present Proof', () => { handshakeProtocols: [HandshakeProtocol.Connections], }) - const faberOptions = getAgentOptions(`Connectionless proofs with mediator Faber-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: faberMediationOutOfBandRecord.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, + }, + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) + ) - const aliceOptions = getAgentOptions(`Connectionless proofs with mediator Alice-${unique}`, { - autoAcceptProofs: AutoAcceptProof.Always, - mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ - domain: 'https://example.com', - }), - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, - }) + const aliceOptions = getAgentOptions( + `Connectionless proofs with mediator Alice-${unique}`, + { + mediatorConnectionsInvite: aliceMediationOutOfBandRecord.outOfBandInvitation.toUrl({ + domain: 'https://example.com', + }), + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + getLegacyAnonCredsModules({ + autoAcceptProofs: AutoAcceptProof.Always, + }) + ) 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 prepareForIssuance(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 issueCredential({ + 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 +357,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 +365,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, @@ -357,9 +399,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 +411,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 f35b4da5d3..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 @@ -1,38 +1,63 @@ -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 { setupProofsTest, waitForProofExchangeRecord } from '../../../../../../tests/helpers' -import testLogger from '../../../../../../tests/logger' -import { DidCommMessageRepository } from '../../../../../storage/didcomm' +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 as AnonCredsProofRequestClass } from '../../../../../../../anoncreds/src/models/AnonCredsProofRequest' +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 setupProofsTest( - 'Faber agent', - 'Alice agent' - )) + ;({ + 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 () => { @@ -46,34 +71,34 @@ 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: 50, + }, + ], }, }, 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, + threadId: aliceProofExchangeRecord.threadId, }) + const proposal = await faberAgent.proofs.findProposalMessage(faberProofExchangeRecord.id) expect(proposal).toMatchObject({ type: 'https://didcomm.org/present-proof/2.0/propose-presentation', formats: [ @@ -94,29 +119,22 @@ 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]: { - name: 'image_0', - restrictions: [ - { - cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - }, - }, + requested_attributes: {}, requested_predicates: { - [predicatesGroup]: { + [Object.keys(proposalAttach.requested_predicates)[0]]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -129,44 +147,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, @@ -174,22 +154,39 @@ describe('Present Proof', () => { 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, + }, + ], + }, + }, }, }, }) 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), @@ -215,34 +212,36 @@ 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: 50, + }, + ], }, }, 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, + threadId: aliceProofExchangeRecord.threadId, + // Negotiation so this will be the second proposal + count: 2, }) - 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: [ { @@ -262,29 +261,20 @@ 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]: { + [Object.keys(proposalAttach2.requested_predicates)[0]]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -298,29 +288,21 @@ 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, + // Negotiation so this will be the second request + count: 2, }) - 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: [ { @@ -350,7 +332,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: [ @@ -372,29 +353,19 @@ 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({ - requested_attributes: { - [attributesGroup]: { - name: 'name', - restrictions: [ - { - cred_def_id: presentationPreview.attributes[1].credentialDefinitionId, - }, - ], - }, - }, + const proposalAttach3 = ( + proposal as V2ProposePresentationMessage + )?.proposalAttachments[0].getDataAsJson() + expect(proposalAttach3).toMatchObject({ + requested_attributes: {}, requested_predicates: { - [predicatesGroup]: { + [Object.keys(proposalAttach3.requested_predicates ?? {})[0]]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: presentationPreview.predicates[0].credentialDefinitionId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -407,24 +378,15 @@ describe('Present Proof', () => { const proofRequest = JsonTransformer.fromJSON( proofRequestMessage.requestAttachments[0].getDataAsJson(), - ProofRequest + AnonCredsProofRequestClass ) 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', - requested_attributes: { - '0': { - name: 'name', - restrictions: [ - { - cred_def_id: credDefId, - }, - ], - }, - }, + requested_attributes: {}, requested_predicates: { [predicateKey]: { name: 'age', @@ -432,7 +394,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..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 @@ -1,29 +1,61 @@ -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, + 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'], + 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 +63,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 +100,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, + 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, + }, + ], + }, + }, }, }, }) @@ -105,13 +148,43 @@ 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', + 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 +201,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 +239,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 +246,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, + 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/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..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 @@ -1,29 +1,82 @@ -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 { EventReplaySubject } from '../../../../../../tests' + +import { + issueLegacyAnonCredsCredential, + setupAnonCredsTests, +} from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' +import { waitForProofExchangeRecord } from '../../../../../../tests' import testLogger from '../../../../../../tests/logger' -import { getGroupKeysFromIndyProofFormatData } from '../../../formats/indy/__tests__/groupKeys' -import { ProofAttributeInfo, AttributeFilter, ProofPredicateInfo, PredicateType } from '../../../formats/indy/models' +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' describe('Present Proof', () => { - let faberAgent: Agent - let aliceAgent: Agent - let credDefId: string - let aliceConnection: ConnectionRecord - let faberConnection: ConnectionRecord + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + 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', 'image_0', 'image_1'], + })) + + await issueLegacyAnonCredsCredential({ + issuerAgent: faberAgent, + holderAgent: aliceAgent, + holderReplay: aliceReplay, + issuerReplay: faberReplay, + issuerHolderConnectionId: faberConnectionId, + offer: { + credentialDefinitionId, + attributes: [ + { + name: 'name', + value: 'John', + }, + { + name: 'age', + 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==' }), + }), + }), + ], + }, + }) }) afterAll(async () => { @@ -43,14 +96,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: 50, + credentialDefinitionId, + }, + ], }, }, }) @@ -217,9 +283,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: { @@ -227,26 +290,23 @@ describe('Present Proof', () => { version: '1.0', nonce: expect.any(String), requested_attributes: { - 0: { + [Object.keys(formatData.proposal?.indy?.requested_attributes ?? {})[0]]: { name: 'name', - }, - [proposeKey1]: { - name: 'image_0', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [proposeKey2]: { + [Object.keys(formatData.proposal?.indy?.requested_predicates ?? {})[0]]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -259,26 +319,23 @@ describe('Present Proof', () => { version: '1.0', nonce: expect.any(String), requested_attributes: { - 0: { + [Object.keys(formatData.request?.indy?.requested_attributes ?? {})[0]]: { name: 'name', - }, - [requestKey1]: { - name: 'image_0', restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, }, requested_predicates: { - [requestKey2]: { + [Object.keys(formatData.request?.indy?.requested_predicates ?? {})[0]]: { name: 'age', p_type: '>=', p_value: 50, restrictions: [ { - cred_def_id: credDefId, + cred_def_id: credentialDefinitionId, }, ], }, @@ -307,39 +364,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 +372,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, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -476,39 +528,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 +536,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, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) @@ -545,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', @@ -564,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', @@ -584,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', @@ -605,39 +652,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 +660,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, + requested_attributes: { + name: { + name: 'name', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + image_0: { + name: 'image_0', + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '>=', + p_value: 50, + restrictions: [ + { + cred_def_id: credentialDefinitionId, + }, + ], + }, + }, }, }, }) diff --git a/packages/core/src/modules/routing/__tests__/mediation.test.ts b/packages/core/src/modules/routing/__tests__/mediation.test.ts index 7c9bfab59f..e792d1c32c 100644 --- a/packages/core/src/modules/routing/__tests__/mediation.test.ts +++ b/packages/core/src/modules/routing/__tests__/mediation.test.ts @@ -7,25 +7,31 @@ 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 { sleep } from '../../../utils/sleep' import { ConnectionRecord, HandshakeProtocol } from '../../connections' import { MediatorPickupStrategy } from '../MediatorPickupStrategy' import { MediationState } from '../models/MediationState' -const recipientAgentOptions = getAgentOptions('Mediation: Recipient', { - indyLedgers: [], -}) -const mediatorAgentOptions = getAgentOptions('Mediation: Mediator', { - autoAcceptMediationRequests: true, - endpoints: ['rxjs:mediator'], - indyLedgers: [], -}) - -const senderAgentOptions = getAgentOptions('Mediation: Sender', { - endpoints: ['rxjs:sender'], - indyLedgers: [], -}) +const recipientAgentOptions = getAgentOptions('Mediation: Recipient', {}, getIndySdkModules()) +const mediatorAgentOptions = getAgentOptions( + 'Mediation: Mediator', + { + autoAcceptMediationRequests: true, + endpoints: ['rxjs:mediator'], + }, + getIndySdkModules() +) + +const senderAgentOptions = getAgentOptions( + 'Mediation: Sender', + { + endpoints: ['rxjs:sender'], + }, + getIndySdkModules() +) describe('mediator establishment', () => { let recipientAgent: Agent @@ -150,27 +156,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, } ) }) @@ -279,5 +285,7 @@ describe('mediator establishment', () => { }) expect(basicMessage.content).toBe(message) + + await recipientAgent.mediationRecipient.stopMessagePickup() }) }) diff --git a/packages/core/src/modules/routing/__tests__/pickup.test.ts b/packages/core/src/modules/routing/__tests__/pickup.test.ts index 8b33fcacf2..c67bac9b89 100644 --- a/packages/core/src/modules/routing/__tests__/pickup.test.ts +++ b/packages/core/src/modules/routing/__tests__/pickup.test.ts @@ -5,20 +5,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 { 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, - indyLedgers: [], -}) -const mediatorOptions = getAgentOptions('Mediation: Mediator Pickup', { - autoAcceptConnections: true, - endpoints: ['wss://mediator'], - indyLedgers: [], -}) +const recipientOptions = getAgentOptions( + 'Mediation: Recipient Pickup', + { + autoAcceptConnections: true, + }, + getIndySdkModules() +) +const mediatorOptions = getAgentOptions( + 'Mediation: Mediator Pickup', + { + autoAcceptConnections: true, + endpoints: ['wss://mediator'], + }, + getIndySdkModules() +) describe('E2E Pick Up protocol', () => { let recipientAgent: Agent 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/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/modules/vc/__tests__/W3cCredentialService.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts index 06e0b42245..f0f08fe07c 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialService.test.ts @@ -1,11 +1,13 @@ import type { AgentContext } from '../../../agent' +import type { Wallet } from '../../../wallet' +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' import { TypedArrayEncoder } from '../../../utils' import { JsonTransformer } from '../../../utils/JsonTransformer' -import { IndyWallet } from '../../../wallet/IndyWallet' import { WalletError } from '../../../wallet/error' import { DidKey } from '../../dids' import { @@ -43,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 @@ -64,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 privateKey = TypedArrayEncoder.fromString('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/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/BaseRecord.ts b/packages/core/src/storage/BaseRecord.ts index 9a8408032d..5b63bcfe86 100644 --- a/packages/core/src/storage/BaseRecord.ts +++ b/packages/core/src/storage/BaseRecord.ts @@ -18,7 +18,10 @@ export type RecordTags = ReturnType + // We want an empty object, as Record will make typescript + // not infer the types correctly + // eslint-disable-next-line @typescript-eslint/ban-types + MetadataValues = {} > { protected _tags: CustomTags = {} as CustomTags diff --git a/packages/core/src/storage/IndyStorageService.ts b/packages/core/src/storage/IndyStorageService.ts deleted file mode 100644 index bd71d1701f..0000000000 --- a/packages/core/src/storage/IndyStorageService.ts +++ /dev/null @@ -1,327 +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) - - record.updatedAt = new Date() - - 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) - - record.updatedAt = new Date() - - 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__/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__/IndyStorageService.test.ts b/packages/core/src/storage/__tests__/IndyStorageService.test.ts deleted file mode 100644 index b517641408..0000000000 --- a/packages/core/src/storage/__tests__/IndyStorageService.test.ts +++ /dev/null @@ -1,323 +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 { sleep } from '../../utils/sleep' -import { IndyWallet } from '../../wallet/IndyWallet' -import { IndyStorageService } from '../IndyStorageService' - -import { TestRecord } from './TestRecord' - -const startDate = Date.now() - -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) - }) - - it('After a save the record should have update the updatedAt property', async () => { - const time = startDate - const record = await insertRecord({ id: 'test-updatedAt' }) - expect(record.updatedAt?.getTime()).toBeGreaterThan(time) - }) - }) - - 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) - }) - - it('After a record has been updated it should have updated the updatedAT property', async () => { - const time = startDate - 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.createdAt.getTime()).toBeGreaterThan(time) - }) - }) - - 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/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/storage/migration/__tests__/0.1.test.ts b/packages/core/src/storage/migration/__tests__/0.1.test.ts index f38ba94a87..cd5fddaccf 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( { @@ -95,6 +101,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( { @@ -153,6 +162,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( { @@ -211,6 +223,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 9fb991253b..1f619b1b57 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' @@ -33,6 +36,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( { @@ -99,6 +105,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( { @@ -143,6 +152,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 124cdf0a88..29ed62d1b9 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__/__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/src/storage/migration/__tests__/backup.test.ts b/packages/core/src/storage/migration/__tests__/backup.test.ts index 8a8b351a38..e582263b57 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..61c2ddb3af 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 @@ -1,9 +1,8 @@ import type { BaseAgent } from '../../../../agent/BaseAgent' -import type { CredentialMetadata, CredentialExchangeRecord } from '../../../../modules/credentials' +import type { CredentialExchangeRecord } from '../../../../modules/credentials' import type { JsonObject } from '../../../../types' import { CredentialState } from '../../../../modules/credentials/models/CredentialState' -import { CredentialMetadataKeys } from '../../../../modules/credentials/repository/CredentialMetadataTypes' import { CredentialRepository } from '../../../../modules/credentials/repository/CredentialRepository' import { Metadata } from '../../../Metadata' import { DidCommMessageRepository, DidCommMessageRecord, DidCommMessageRole } from '../../../didcomm' @@ -122,27 +121,25 @@ export async function updateIndyMetadata( agent.config.logger.debug(`Updating indy metadata to use the generic metadata api available to records.`) const { requestMetadata, schemaId, credentialDefinitionId, ...rest } = credentialRecord.metadata.data - const metadata = new Metadata(rest) + const metadata = new Metadata>(rest) + const indyRequestMetadataKey = '_internal/indyRequest' + const indyCredentialMetadataKey = '_internal/indyCredential' if (requestMetadata) { - agent.config.logger.trace( - `Found top-level 'requestMetadata' key, moving to '${CredentialMetadataKeys.IndyRequest}'` - ) - metadata.add(CredentialMetadataKeys.IndyRequest, { ...requestMetadata }) + agent.config.logger.trace(`Found top-level 'requestMetadata' key, moving to '${indyRequestMetadataKey}'`) + metadata.add(indyRequestMetadataKey, { ...requestMetadata }) } if (schemaId && typeof schemaId === 'string') { - agent.config.logger.trace( - `Found top-level 'schemaId' key, moving to '${CredentialMetadataKeys.IndyCredential}.schemaId'` - ) - metadata.add(CredentialMetadataKeys.IndyCredential, { schemaId }) + agent.config.logger.trace(`Found top-level 'schemaId' key, moving to '${indyCredentialMetadataKey}.schemaId'`) + metadata.add(indyCredentialMetadataKey, { schemaId }) } if (credentialDefinitionId && typeof credentialDefinitionId === 'string') { agent.config.logger.trace( - `Found top-level 'credentialDefinitionId' key, moving to '${CredentialMetadataKeys.IndyCredential}.credentialDefinitionId'` + `Found top-level 'credentialDefinitionId' key, moving to '${indyCredentialMetadataKey}.credentialDefinitionId'` ) - metadata.add(CredentialMetadataKeys.IndyCredential, { credentialDefinitionId }) + metadata.add(indyCredentialMetadataKey, { credentialDefinitionId }) } credentialRecord.metadata = metadata @@ -170,7 +167,7 @@ export async function updateIndyMetadata( * "credentials": [ * { * "credentialRecordId": "09e46da9-a575-4909-b016-040e96c3c539", - * "credentialRecordType": "indy", + * "credentialRecordType": "anoncreds" * } * ] * } diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 23fe599c84..f621d4393e 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/__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/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..a432b6bc6c 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -5,13 +5,10 @@ export * from './MultiBaseEncoder' 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/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/IndyWallet.test.ts b/packages/core/src/wallet/IndyWallet.test.ts deleted file mode 100644 index 8a600a2592..0000000000 --- a/packages/core/src/wallet/IndyWallet.test.ts +++ /dev/null @@ -1,132 +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 privateKey = TypedArrayEncoder.fromString('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 from private key', async () => { - const key = await indyWallet.createKey({ - privateKey: TypedArrayEncoder.fromString('2103de41b4ae37e8e28586d84a342b67'), - keyType: KeyType.Ed25519, - }) - - expect(key).toMatchObject({ - keyType: KeyType.Ed25519, - }) - }) - - test('Fail to create ed25519 keypair from seed', async () => { - await expect(indyWallet.createKey({ privateKey, keyType: KeyType.Ed25519 })).rejects.toThrowError(WalletError) - }) - - test('Fail to create x25519 keypair', async () => { - await expect(indyWallet.createKey({ privateKey, 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 8c8e83d575..0000000000 --- a/packages/core/src/wallet/IndyWallet.ts +++ /dev/null @@ -1,703 +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 private key and keyType. - * The keypair is also automatically stored in the wallet afterwards - * - * Bls12381g1g2 and X25519 are not supported. - * - * @param privateKey Buffer Private key (formerly called 'seed') - * @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, privateKey, keyType }: WalletCreateKeyOptions): Promise { - try { - if (seed && privateKey) { - throw new AriesFrameworkError('Only one of seed and privateKey can be set') - } - - // Ed25519 is supported natively in Indy wallet - if (keyType === KeyType.Ed25519) { - if (seed) { - throw new AriesFrameworkError( - 'IndyWallet does not support seed. You may rather want to specify a private key for deterministic ed25519 key generation' - ) - } - - const verkey = await this.indy.createKey(this.handle, { - seed: privateKey?.toString(), - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - 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, privateKey }) - 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/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/agents.test.ts b/packages/core/tests/agents.test.ts index 47393e371b..9bded8ba18 100644 --- a/packages/core/tests/agents.test.ts +++ b/packages/core/tests/agents.test.ts @@ -1,22 +1,27 @@ /* 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 aliceAgentOptions = getAgentOptions( + 'Agents Alice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) +const bobAgentOptions = getAgentOptions( + 'Agents Bob', + { + endpoints: ['rxjs:bob'], + }, + getIndySdkModules() +) describe('agents', () => { let aliceAgent: Agent @@ -32,22 +37,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..2525879598 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,46 @@ 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 faberAgentOptions = getAgentOptions( + 'Faber Agent Connections', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() + ) + const aliceAgentOptions = getAgentOptions( + 'Alice Agent Connections', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() + ) + const acmeAgentOptions = getAgentOptions( + 'Acme Agent Connections', + { + endpoints: ['rxjs:acme'], + }, + getIndySdkModules() + ) + const mediatorAgentOptions = getAgentOptions( + 'Mediator Agent Connections', + { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }, + getIndySdkModules() + ) - 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/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/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 e8fd1f8a29..1ae3f39709 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -1,76 +1,52 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { SubjectMessage } from '../../../tests/transport/SubjectInboundTransport' import type { - AcceptCredentialOfferOptions, AgentDependencies, + BaseEvent, BasicMessage, BasicMessageStateChangedEvent, ConnectionRecordProps, - CredentialDefinitionTemplate, CredentialStateChangedEvent, InitConfig, InjectionToken, ProofStateChangedEvent, - SchemaTemplate, Wallet, + Agent, + CredentialState, } 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 { 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' -import { firstValueFrom, ReplaySubject, Subject } 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 { 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 { agentDependencies, IndySdkPostgresWalletScheme } 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 +58,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( @@ -91,25 +67,16 @@ 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, }, publicDidSeed, autoAcceptConnections: true, - connectToIndyLedgersOnStartup: false, - indyLedgers: [ - { - id: `pool-${name}`, - isProduction: false, - genesisPath, - indyNamespace: `pool:localtest`, - transactionAuthorAgreement: { version: taaVersion, acceptanceMechanism: taaAcceptanceMechanism }, - }, - ], // TODO: determine the log level based on an environment variable. This will make it // possible to run e.g. failed github actions in debug mode for extra logs logger: TestLogger.fromLogger(testLogger, name), @@ -119,17 +86,22 @@ export function getAgentOptions = {}) { +export function getPostgresAgentOptions( + name: string, + extraConfig: Partial = {}, + modules?: AgentModules +) { const config: InitConfig = { label: `Agent: ${name}`, walletConfig: { - id: `Wallet${name}`, + // 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', @@ -142,19 +114,11 @@ export function getPostgresAgentOptions(name: string, extraConfig: Partial + 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 + export function waitForProofExchangeRecordSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, parentThreadId, state, previousState, timeoutMs = 10000, + count = 1, }: { threadId?: string parentThreadId?: string state?: ProofState previousState?: ProofState | null timeoutMs?: number + count?: number } ) { - const observable: Observable = - subject instanceof ReplaySubject ? subject.asObservable() : subject - return firstValueFrom( + const observable: Observable = subject instanceof ReplaySubject ? subject.asObservable() : subject + return lastValueFrom( 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), @@ -234,13 +209,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) ) ) @@ -259,7 +235,7 @@ export async function waitForTrustPingReceivedEvent( } export function waitForTrustPingReceivedEventSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, timeoutMs = 10000, @@ -271,6 +247,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 +277,7 @@ export async function waitForTrustPingResponseReceivedEvent( } export function waitForTrustPingResponseReceivedEventSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, timeoutMs = 10000, @@ -312,6 +289,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 +305,7 @@ export function waitForTrustPingResponseReceivedEventSubject( } export function waitForCredentialRecordSubject( - subject: ReplaySubject | Observable, + subject: ReplaySubject | Observable, { threadId, state, @@ -344,6 +322,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 +457,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 +476,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/index.ts b/packages/core/tests/index.ts new file mode 100644 index 0000000000..2822fb23e1 --- /dev/null +++ b/packages/core/tests/index.ts @@ -0,0 +1,9 @@ +export * from './jsonld' +export * from './transport' +export * from './events' +export * from './helpers' +export * from './indySdk' + +import testLogger from './logger' + +export { testLogger } diff --git a/packages/core/tests/indySdk.ts b/packages/core/tests/indySdk.ts new file mode 100644 index 0000000000..b5e5a3075d --- /dev/null +++ b/packages/core/tests/indySdk.ts @@ -0,0 +1,3 @@ +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' + +export { indySdk } diff --git a/packages/core/tests/jsonld.ts b/packages/core/tests/jsonld.ts new file mode 100644 index 0000000000..354599cfb3 --- /dev/null +++ b/packages/core/tests/jsonld.ts @@ -0,0 +1,171 @@ +import type { EventReplaySubject } from './events' +import type { AutoAcceptCredential, AutoAcceptProof, ConnectionRecord } from '../src' + +import { BbsModule } from '../../bbs-signatures/src/BbsModule' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +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 }), + }), + indySdk: new IndySdkModule({ + indySdk, + }), + bbs: new BbsModule(), + } 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 = verifierName + ? new Agent( + getAgentOptions( + verifierName, + { + endpoints: ['rxjs:verifier'], + }, + modules + ) + ) + : undefined + + setupSubjectTransports(verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent]) + const [issuerReplay, holderReplay, verifierReplay] = setupEventReplaySubjects( + verifierAgent ? [issuerAgent, holderAgent, verifierAgent] : [issuerAgent, holderAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + + await issuerAgent.initialize() + await holderAgent.initialize() + if (verifierAgent) 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 (verifierAgent) { + ;[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/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..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, indyLedgers: [] }) +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..4f7596d5ff 100644 --- a/packages/core/tests/multi-protocol-version.test.ts +++ b/packages/core/tests/multi-protocol-version.test.ts @@ -1,23 +1,30 @@ -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 aliceAgentOptions = getAgentOptions( + 'Multi Protocol Versions - Alice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) +const bobAgentOptions = getAgentOptions( + 'Multi Protocol Versions - Bob', + { + endpoints: ['rxjs:bob'], + }, + getIndySdkModules() +) describe('multi version protocols', () => { let aliceAgent: Agent @@ -31,29 +38,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..6468d27d7f 100644 --- a/packages/core/tests/oob-mediation-provision.test.ts +++ b/packages/core/tests/oob-mediation-provision.test.ts @@ -1,28 +1,37 @@ /* 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 faberAgentOptions = getAgentOptions( + 'OOB mediation provision - Faber Agent', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) +const aliceAgentOptions = getAgentOptions( + 'OOB mediation provision - Alice Recipient Agent', + { + endpoints: ['rxjs:alice'], + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + getIndySdkModules() +) +const mediatorAgentOptions = getAgentOptions( + 'OOB mediation provision - Mediator Agent', + { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }, + getIndySdkModules() +) describe('out of band with mediation set up with provision method', () => { const makeConnectionConfig = { @@ -40,32 +49,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..72d92ea0de 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,31 @@ 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 faberAgentOptions = getAgentOptions( + 'OOB mediation - Faber Agent', + { + endpoints: ['rxjs:faber'], + }, + getIndySdkModules() +) +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, + }, + getIndySdkModules() +) +const mediatorAgentOptions = getAgentOptions( + 'OOB mediation - Mediator Agent', + { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }, + getIndySdkModules() +) describe('out of band with mediation', () => { const makeConnectionConfig = { diff --git a/packages/core/tests/oob.test.ts b/packages/core/tests/oob.test.ts index 94161fd838..836ca766bd 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,26 @@ import { DidCommMessageRepository, DidCommMessageRole } from '../src/storage' import { JsonEncoder } from '../src/utils' import { TestMessage } from './TestMessage' -import { getAgentOptions, prepareForIssuance, 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 faberAgentOptions = getAgentOptions( + 'Faber Agent OOB', + { + endpoints: ['rxjs:faber'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) +const aliceAgentOptions = getAgentOptions( + 'Alice Agent OOB', + { + endpoints: ['rxjs:alice'], + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) describe('out of band', () => { const makeConnectionConfig = { @@ -56,9 +64,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 +87,34 @@ describe('out of band', () => { aliceAgent.registerOutboundTransport(new SubjectOutboundTransport(subjectMap)) await aliceAgent.initialize() - const { definition } = await prepareForIssuance(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..244eacd496 100644 --- a/packages/core/tests/proofs-sub-protocol.test.ts +++ b/packages/core/tests/proofs-sub-protocol.test.ts @@ -1,33 +1,58 @@ -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 { EventReplaySubject } from './events' +import type { AnonCredsTestsAgent } 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' -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 aliceProofExchangeRecord: ProofExchangeRecord - let presentationPreview: V1PresentationPreview + let faberAgent: AnonCredsTestsAgent + let faberReplay: EventReplaySubject + let aliceAgent: AnonCredsTestsAgent + let aliceReplay: EventReplaySubject + let credentialDefinitionId: string + let faberConnectionId: string + let aliceConnectionId: string 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, + 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 () => { @@ -49,16 +74,29 @@ describe('Present Proof Subprotocol', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v1', parentThreadId, proofFormats: { 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, + }, + ], }, }, }) @@ -118,30 +156,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 +164,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, + 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, + }, + ], + }, + }, }, }, }) @@ -216,16 +250,29 @@ describe('Present Proof Subprotocol', () => { state: ProofState.ProposalReceived, }) - aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ - connectionId: aliceConnection.id, + const aliceProofExchangeRecord = await aliceAgent.proofs.proposeProof({ + connectionId: aliceConnectionId, protocolVersion: 'v2', parentThreadId, proofFormats: { 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, + }, + ], }, }, }) @@ -285,30 +332,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 +340,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, + 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/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)) + } +} diff --git a/packages/core/tests/wallet.test.ts b/packages/core/tests/wallet.test.ts index 3362741146..2168ce72ac 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,8 @@ import { WalletNotFoundError } from '../src/wallet/error/WalletNotFoundError' import { getAgentOptions } from './helpers' -const aliceAgentOptions = getAgentOptions('wallet-tests-Alice') -const bobAgentOptions = getAgentOptions('wallet-tests-Bob') +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/package.json b/packages/indy-sdk/package.json index 7646c74776..8e6acc74c4 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 20574f3d46..d099591543 100644 --- a/packages/indy-sdk/src/IndySdkModule.ts +++ b/packages/indy-sdk/src/IndySdkModule.ts @@ -1,15 +1,16 @@ import type { IndySdkModuleConfigOptions } from './IndySdkModuleConfig' -import type { DependencyManager, Module } from '@aries-framework/core' +import type { AgentContext, DependencyManager, Module } from '@aries-framework/core' import { AnonCredsHolderServiceSymbol, 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' +import { IndySdkPoolService } from './ledger' import { IndySdkStorageService } from './storage' import { IndySdkSymbol } from './types' import { IndySdkWallet } from './wallet' @@ -24,12 +25,36 @@ export class IndySdkModule implements Module { public register(dependencyManager: DependencyManager) { dependencyManager.registerInstance(IndySdkSymbol, this.config.indySdk) + // 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(IndySdkPoolService) dependencyManager.registerSingleton(AnonCredsIssuerServiceSymbol, IndySdkIssuerService) 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 664b86e4de..8b9157221a 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -322,6 +322,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const request = await indySdk.buildCredDefRequest(options.credentialDefinition.issuerId, { id: credentialDefinitionId, + // 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, @@ -447,7 +448,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 c043673cd1..7f7cf38ebd 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, @@ -8,37 +9,28 @@ import type { DidDeactivateResult, DidUpdateResult, Key, + Buffer, } 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 privateKey = options.secret?.privateKey @@ -70,16 +62,14 @@ 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: privateKey?.toString(), }) 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 = this.indySdkPoolService.getPoolForNamespace(indyNamespace) + const pool = indySdkPoolService.getPoolForNamespace(indyNamespace) await this.registerPublicDid(agentContext, unqualifiedSubmitterDid, unqualifiedIndyDid, verkey, alias, pool, role) // Create did document @@ -104,7 +94,6 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { // Save the did so we know we created it and can issue with it const didRecord = new DidRecord({ - id: qualifiedSovDid, did: qualifiedSovDid, role: DidDocumentRole.Created, tags: { @@ -112,7 +101,7 @@ export class IndySdkSovDidRegistrar implements DidRegistrar { qualifiedIndyDid, }, }) - await this.didRepository.save(agentContext, didRecord) + await didRepository.save(agentContext, didRecord) return { didDocumentMetadata: { @@ -178,12 +167,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 +206,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..ab3f31bd4f --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts @@ -0,0 +1,383 @@ +import type { IndySdkPool } from '../../ledger/IndySdkPool' +import type { Wallet, DidRecord, RecordSavedEvent } from '@aries-framework/core' + +import { + TypedArrayEncoder, + DidRepository, + SigningProviderRegistry, + JsonTransformer, + DidDocumentRole, + EventEmitter, + RepositoryEventTypes, +} from '@aries-framework/core' +import indySdk from 'indy-sdk' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { mockFunction, getAgentConfig, getAgentContext, agentDependencies, mockProperty } from '../../../../core/tests' +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 indySdkPoolServiceMock = new IndySdkPoolServiceMock() + +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { 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 createDidMock = jest.fn(async () => ['R1xKJw17sUoXhejEpugMYJ', 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu']) +mockProperty(wallet, 'handle', 10) + +const agentContext = getAgentContext({ + wallet, + registerInstances: [ + [DidRepository, didRepository], + [IndySdkPoolService, indySdkPoolServiceMock], + [IndySdkSymbol, { createAndStoreMyDid: createDidMock }], + ], + agentConfig, +}) + +const indySdkSovDidRegistrar = new IndySdkSovDidRegistrar() + +describe('IndySdkSovDidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + it('should return an error state if an invalid private key is provided', async () => { + const result = await indySdkSovDidRegistrar.create(agentContext, { + method: 'sov', + + options: { + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + privateKey: TypedArrayEncoder.fromString('invalid'), + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Invalid private key 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, + registerInstances: [ + [DidRepository, didRepository], + [IndySdkPoolService, indySdkPoolServiceMock], + [IndySdkSymbol, indySdk], + ], + }) + + const result = await indySdkSovDidRegistrar.create(agentContext, { + method: 'sov', + + options: { + submitterDid: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + privateKey: TypedArrayEncoder.fromString('12345678901234567890123456789012'), + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: Expected wallet to be instance of IndySdkWallet, 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 privateKey = '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: { + privateKey: TypedArrayEncoder.fromString(privateKey), + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + // Alias + 'Hello', + // Pool + { config: { 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: { + privateKey, + }, + }, + }) + }) + + it('should correctly create a did:sov document with services', async () => { + const privateKey = '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: { + privateKey: TypedArrayEncoder.fromString(privateKey), + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + // Alias + 'Hello', + // Pool + { config: { 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: { + privateKey, + }, + }, + }) + }) + + it('should store the did document', async () => { + const privateKey = TypedArrayEncoder.fromString('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: { + privateKey, + }, + }) + + expect(saveCalled).toHaveBeenCalledTimes(1) + const [saveEvent] = saveCalled.mock.calls[0] + + expect(saveEvent.payload.record).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..c9bb1bbc93 --- /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 { SigningProviderRegistry, JsonTransformer } from '@aries-framework/core' +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' +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 indySdkPoolServiceMock = new IndySdkPoolServiceMock() + +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { indyNamespace: 'pool1' }, +} as IndySdkPool) + +const agentConfig = getAgentConfig('IndySdkSovDidResolver') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) + +const agentContext = getAgentContext({ + wallet, + agentConfig, + registerInstances: [ + [IndySdkPoolService, indySdkPoolServiceMock], + [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 dfa25feb37..b784600416 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPool.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPool.ts @@ -16,12 +16,19 @@ 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 - id: string + isProduction: boolean indyNamespace: string transactionAuthorAgreement?: TransactionAuthorAgreement + connectOnStartup?: boolean } export class IndySdkPool { @@ -30,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( @@ -84,7 +91,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 +110,7 @@ export class IndySdkPool { } private async connectToLedger() { - const poolName = this.poolConfig.id + const poolName = this.poolConfig.id ?? this.poolConfig.indyNamespace const genesisPath = await this.getGenesisPath() if (!genesisPath) { @@ -115,7 +122,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 @@ -128,7 +135,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 } @@ -142,7 +149,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 +161,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 @@ -168,9 +179,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 } @@ -180,7 +190,7 @@ export class IndySdkPool { if (this.poolConfig.genesisPath) return this.poolConfig.genesisPath // Determine the genesisPath - const genesisPath = this.fileSystem.tempPath + `/genesis-${this.poolConfig.id}.txn` + const genesisPath = this.fileSystem.tempPath + `/genesis-${this.poolConfig.id ?? 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..16bc0ac6a2 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 { IndySdk } from '../types' 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 { 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,25 @@ export class IndySdkPoolService { private indySdk: IndySdk private stop$: Subject private fileSystem: FileSystem + private indySdkModuleConfig: IndySdkModuleConfig public constructor( - 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.indySdk = indySdkModuleConfig.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 +64,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 } } @@ -99,7 +85,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 } ) } @@ -126,8 +112,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 } } @@ -164,7 +150,7 @@ export class IndySdkPoolService { const pool = this.pools.find((pool) => pool.didIndyNamespace === indyNamespace) if (!pool) { - throw new IndySdkPoolNotFoundError(`No ledgers found for IndyNamespace '${indyNamespace}'.`) + throw new IndySdkPoolNotFoundError(`No ledgers found for indy namespace '${indyNamespace}'.`) } return pool @@ -290,14 +276,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 +291,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..ba96c32b48 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,21 @@ 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: 1 }) + +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(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 +75,18 @@ 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) + afterEach(() => { + cache.clear() }) 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 +117,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 +127,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 +146,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 +164,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 +182,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 +201,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 +215,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 +238,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 +247,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 +292,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/IndySdkStorageService.ts b/packages/indy-sdk/src/storage/IndySdkStorageService.ts index 48f3022154..bfcb740d79 100644 --- a/packages/indy-sdk/src/storage/IndySdkStorageService.ts +++ b/packages/indy-sdk/src/storage/IndySdkStorageService.ts @@ -139,6 +139,8 @@ export class IndySdkStorageService implements StorageServi public async save(agentContext: AgentContext, record: T) { assertIndySdkWallet(agentContext.wallet) + record.updatedAt = new Date() + const value = JsonTransformer.serialize(record) const tags = this.transformFromRecordTagValues(record.getTags()) as Record @@ -158,6 +160,8 @@ export class IndySdkStorageService implements StorageServi public async update(agentContext: AgentContext, record: T): Promise { assertIndySdkWallet(agentContext.wallet) + record.updatedAt = new Date() + const value = JsonTransformer.serialize(record) const tags = this.transformFromRecordTagValues(record.getTags()) as Record diff --git a/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts b/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts index 7a8855c9d5..baa7207d7f 100644 --- a/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts +++ b/packages/indy-sdk/src/storage/__tests__/IndySdkStorageService.test.ts @@ -1,30 +1,29 @@ 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) +const startDate = Date.now() + +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 +56,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 +73,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', @@ -111,6 +110,12 @@ describe('IndySdkStorageService', () => { expect(record).toEqual(found) }) + + it('After a save the record should have update the updatedAt property', async () => { + const time = startDate + const record = await insertRecord({ id: 'test-updatedAt' }) + expect(record.updatedAt?.getTime()).toBeGreaterThan(time) + }) }) describe('getById()', () => { @@ -149,6 +154,18 @@ describe('IndySdkStorageService', () => { const retrievedRecord = await storageService.getById(agentContext, TestRecord, record.id) expect(retrievedRecord).toEqual(record) }) + + it('After a record has been updated it should have updated the updatedAT property', async () => { + const time = startDate + 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.createdAt.getTime()).toBeGreaterThan(time) + }) }) describe('delete()', () => { @@ -237,13 +254,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/src/wallet/IndySdkWallet.ts b/packages/indy-sdk/src/wallet/IndySdkWallet.ts index 66d75e8933..043e079c05 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/src/wallet/__tests__/IndySdkWallet.test.ts b/packages/indy-sdk/src/wallet/__tests__/IndySdkWallet.test.ts index 1bb5447031..c80ea47b4a 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) }) @@ -118,7 +118,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/indy-sdk-anoncreds-registry.e2e.test.ts b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts index c6b7ced0f1..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 @@ -1,39 +1,26 @@ import { Agent } from '@aries-framework/core' -import indySdk from 'indy-sdk' -import { Subject } from 'rxjs' import { agentDependencies, getAgentConfig } from '../../core/tests/helpers' import { IndySdkModule } from '../src' import { IndySdkAnonCredsRegistry } from '../src/anoncreds/services/IndySdkAnonCredsRegistry' -import { IndySdkPoolService } from '../src/ledger/IndySdkPoolService' -const agentConfig = getAgentConfig('IndySdkAnonCredsRegistry') +import { getIndySdkModuleConfig } from './setupIndySdkModule' -const indySdkPoolService = new IndySdkPoolService( - indySdk, - agentConfig.logger, - new Subject(), - new agentConfig.agentDependencies.FileSystem() -) +const agentConfig = getAgentConfig('IndySdkAnonCredsRegistry') const agent = new Agent({ config: agentConfig, dependencies: agentDependencies, modules: { - indySdk: new IndySdkModule({ - indySdk, - }), + indySdk: new IndySdkModule(getIndySdkModuleConfig()), }, }) -agent.dependencyManager.registerInstance(IndySdkPoolService, indySdkPoolService) -indySdkPoolService.setPools(agentConfig.indyLedgers) const indySdkAnonCredsRegistry = new IndySdkAnonCredsRegistry() describe('IndySdkAnonCredsRegistry', () => { beforeAll(async () => { await agent.initialize() - await indySdkPoolService.connectToPools() }) afterAll(async () => { @@ -41,7 +28,8 @@ describe('IndySdkAnonCredsRegistry', () => { await agent.wallet.delete() }) - test('it works! :)', async () => { + // One test as the credential definition depends on the schema + test('register and resolve a schema and credential definition', async () => { const dynamicVersion = `1.${Math.random() * 100}` const schemaResult = await indySdkAnonCredsRegistry.registerSchema(agent.context, { @@ -52,7 +40,7 @@ describe('IndySdkAnonCredsRegistry', () => { version: dynamicVersion, }, options: { - didIndyNamespace: agentConfig.indyLedgers[0].indyNamespace, + didIndyNamespace: 'pool:localtest', }, }) diff --git a/packages/core/tests/postgres.e2e.test.ts b/packages/indy-sdk/tests/postgres.e2e.test.ts similarity index 73% rename from packages/core/tests/postgres.e2e.test.ts rename to packages/indy-sdk/tests/postgres.e2e.test.ts index eedffe43b5..a59359f9d8 100644 --- a/packages/core/tests/postgres.e2e.test.ts +++ b/packages/indy-sdk/tests/postgres.e2e.test.ts @@ -1,30 +1,39 @@ /* 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 { 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'], -}) +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 { getIndySdkModules } from './setupIndySdkModule' + +const alicePostgresAgentOptions = getPostgresAgentOptions( + 'AgentsAlice', + { + endpoints: ['rxjs:alice'], + }, + getIndySdkModules() +) + +const bobPostgresAgentOptions = getPostgresAgentOptions( + 'AgentsBob', + { + endpoints: ['rxjs:bob'], + }, + getIndySdkModules() +) describe('postgres agents', () => { let aliceAgent: Agent let bobAgent: Agent let aliceConnection: ConnectionRecord - let bobConnection: ConnectionRecord afterAll(async () => { await bobAgent.shutdown() @@ -42,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', @@ -57,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)) @@ -76,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) }) test('send a message to connection', async () => { diff --git a/packages/indy-sdk/tests/setupIndySdkModule.ts b/packages/indy-sdk/tests/setupIndySdkModule.ts new file mode 100644 index 0000000000..c0e8ee1313 --- /dev/null +++ b/packages/indy-sdk/tests/setupIndySdkModule.ts @@ -0,0 +1,29 @@ +import { DidsModule, KeyDidRegistrar, KeyDidResolver, utils } from '@aries-framework/core' +import indySdk from 'indy-sdk' + +import { genesisPath, taaVersion, taaAcceptanceMechanism } from '../../core/tests/helpers' +import { IndySdkModule, IndySdkModuleConfig, IndySdkSovDidRegistrar, IndySdkSovDidResolver } from '../src' + +export { indySdk } + +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(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 new file mode 100644 index 0000000000..de121b462d --- /dev/null +++ b/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts @@ -0,0 +1,125 @@ +import type { IndySdkSovDidCreateOptions } from '../src/dids/IndySdkSovDidRegistrar' + +import { Agent, TypedArrayEncoder, convertPublicKeyToX25519, JsonTransformer } from '@aries-framework/core' +import { generateKeyPairFromSeed } from '@stablelib/ed25519' + +import { getAgentOptions } from '../../core/tests/helpers' +import { indyDidFromPublicKeyBase58 } from '../src/utils/did' + +import { getIndySdkModules } from './setupIndySdkModule' + +const agentOptions = getAgentOptions('Faber Dids Registrar', {}, getIndySdkModules()) + +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 privateKey = TypedArrayEncoder.fromString( + Array(32 + 1) + .join((Math.random().toString(36) + '00000000000000000').slice(2, 18)) + .slice(0, 32) + ) + + const publicKeyEd25519 = generateKeyPairFromSeed(privateKey).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: { + privateKey, + }, + }) + + expect(JsonTransformer.toJSON(did)).toMatchObject({ + didDocumentMetadata: { + qualifiedIndyDid: `did:indy:pool:localtest:${indyDid}`, + }, + didRegistrationMetadata: { + didIndyNamespace: 'pool:localtest', + }, + 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: { + privateKey: privateKey.toString(), + }, + }, + }) + }) +}) 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..dfa3281214 --- /dev/null +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -0,0 +1,74 @@ +import type { IndySdkSovDidCreateOptions } from '../src/dids/IndySdkSovDidRegistrar' + +import { Agent, AriesFrameworkError, JsonTransformer } from '@aries-framework/core' + +import { getAgentOptions } from '../../core/tests/helpers' + +import { getIndySdkModules } from './setupIndySdkModule' + +const agent = new Agent(getAgentOptions('Indy SDK Sov DID resolver', {}, getIndySdkModules())) + +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/indy-vdr/package.json b/packages/indy-vdr/package.json index fa1c9d3e39..088c8da018 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", "@stablelib/ed25519": "^1.0.2", "rimraf": "^4.0.7", "rxjs": "^7.2.0", diff --git a/packages/indy-vdr/src/IndyVdrModule.ts b/packages/indy-vdr/src/IndyVdrModule.ts index cd6b7244bf..44dc54ff8a 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' @@ -32,4 +32,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/anoncreds/IndyVdrAnonCredsRegistry.ts b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts index ba105104fa..209f0aac81 100644 --- a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts +++ b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts @@ -320,9 +320,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { schemaId: `${schemaMetadata.indyLedgerSeqNo}`, type: 'CL', tag: options.credentialDefinition.tag, - value: { - primary: options.credentialDefinition.value, - }, + value: options.credentialDefinition.value, }, }) @@ -338,7 +336,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 99ba0d1b06..e5dfaade1a 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 } @@ -56,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 3fa6177465..1ad1b0f80a 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)) } /** @@ -102,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 } ) } @@ -165,7 +150,7 @@ export class IndyVdrPoolService { const pool = this.pools.find((pool) => pool.indyNamespace === indyNamespace) if (!pool) { - throw new IndyVdrError(`No ledgers found for IndyNamespace '${indyNamespace}'.`) + throw new IndyVdrError(`No ledgers found for indy namespace '${indyNamespace}'.`) } return pool 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 f7d3bbc9fa..74676ef88b 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) @@ -23,7 +34,6 @@ agent.dependencyManager.registerInstance(IndyVdrPoolService, indyVdrPoolService) describe('IndyVdrAnonCredsRegistry', () => { beforeAll(async () => { await agent.initialize() - await indyVdrPoolService.connectToPools() }) afterAll(async () => { @@ -159,18 +169,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 d148744502..ec6724d576 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({ privateKey: TypedArrayEncoder.fromString('000000000000000000000000Trustee9'), 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 a40d4f72b4..bc0e5f4ea8 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 @@ -2,7 +2,6 @@ import type { Key } from '@aries-framework/core' import { TypedArrayEncoder, - IndyWallet, CacheModuleConfig, InMemoryLruCache, JsonTransformer, @@ -11,8 +10,10 @@ import { } from '@aries-framework/core' 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 { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { IndyVdrSovDidResolver } from '../src/dids' import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' @@ -20,7 +21,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 }) @@ -41,11 +42,7 @@ const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolSer describe('indy-vdr DID Resolver E2E', () => { beforeAll(async () => { - await indyVdrPoolService.connectToPools() - - if (agentConfig.walletConfig) { - await wallet.createAndOpen(agentConfig.walletConfig) - } + await wallet.createAndOpen(agentConfig.walletConfig) signerKey = await wallet.createKey({ privateKey: TypedArrayEncoder.fromString('000000000000000000000000Trustee9'), 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 ee1faad9dc..151b226a70 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 { TypedArrayEncoder, IndyWallet, KeyType, SigningProviderRegistry } from '@aries-framework/core' +import { TypedArrayEncoder, KeyType, SigningProviderRegistry } from '@aries-framework/core' import { GetNymRequest, NymRequest, SchemaRequest, CredentialDefinitionRequest } from '@hyperledger/indy-vdr-shared' -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 { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { IndyVdrPool } from '../src/pool' import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' @@ -12,8 +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 }) @@ -28,11 +29,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({ privateKey: TypedArrayEncoder.fromString('000000000000000000000000Trustee9'), diff --git a/packages/node/bin/is-indy-installed.js b/packages/node/bin/is-indy-installed.js deleted file mode 100755 index 59704d4de7..0000000000 --- a/packages/node/bin/is-indy-installed.js +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable no-console, @typescript-eslint/no-var-requires, no-undef */ - -const { createWallet, deleteWallet } = require('indy-sdk') - -const uuid = Math.random() * 10000 -const id = `test-wallet-id-${uuid}` - -createWallet({ id }, { key: id }) - .then(() => deleteWallet({ id }, { key: id })) - .then(() => { - console.log('Libindy was installed correctly') - }) - .catch((e) => { - console.log('Libindy was installed correctly, but an error did occur') - console.error(e) - }) diff --git a/packages/node/package.json b/packages/node/package.json index 30ffadd1f6..e82409ea98 100644 --- a/packages/node/package.json +++ b/packages/node/package.json @@ -4,8 +4,7 @@ "types": "build/index", "version": "0.3.3", "files": [ - "build", - "bin" + "build" ], "license": "Apache-2.0", "publishConfig": { @@ -17,9 +16,6 @@ "url": "https://github.com/hyperledger/aries-framework-javascript", "directory": "packages/node" }, - "bin": { - "is-indy-installed": "bin/is-indy-installed.js" - }, "scripts": { "build": "yarn run clean && yarn run compile", "clean": "rimraf ./build", @@ -32,7 +28,6 @@ "@types/express": "^4.17.15", "express": "^4.17.1", "ffi-napi": "^4.0.3", - "indy-sdk": "^1.16.0-dev-1636", "node-fetch": "^2.6.1", "ref-napi": "^3.0.3", "ws": "^7.5.3" 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 5e58035b32..fbe7ed0452 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -1,12 +1,11 @@ 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' 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' @@ -15,14 +14,13 @@ const agentDependencies: AgentDependencies = { fetch, EventEmitterClass: EventEmitter, WebSocketClass: WebSocket, - indy, } export { agentDependencies, HttpInboundTransport, WsInboundTransport, - loadPostgresPlugin, - IndyPostgresStorageConfig, - WalletScheme, + loadIndySdkPostgresPlugin, + IndySdkPostgresStorageConfig, + IndySdkPostgresWalletScheme, } diff --git a/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts b/packages/openid4vc-client/tests/openid4vc-client.e2e.test.ts index 20b2898c84..2afc542a49 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/src/__tests__/QuestionAnswerService.test.ts b/packages/question-answer/src/__tests__/QuestionAnswerService.test.ts index bd3f071a01..4a21491d36 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 { 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, @@ -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/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/react-native/package.json b/packages/react-native/package.json index 1da533811d..653c19627e 100644 --- a/packages/react-native/package.json +++ b/packages/react-native/package.json @@ -30,8 +30,7 @@ "@types/react-native": "^0.64.10" }, "devDependencies": { - "@types/indy-sdk-react-native": "npm:@types/indy-sdk@1.16.26", - "indy-sdk-react-native": "^0.3.1", + "@types/react-native": "^0.64.10", "react": "17.0.1", "react-native": "0.64.2", "react-native-fs": "^2.18.0", @@ -40,7 +39,6 @@ "typescript": "~4.9.4" }, "peerDependencies": { - "indy-sdk-react-native": "^0.3.1", "react-native-fs": "^2.18.0", "react-native-get-random-values": "^1.7.0" } 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/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/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 { - let bobAgent: Agent<{ - dummy: DummyModule - }> - let aliceAgent: Agent<{ - dummy: DummyModule - }> + let bobAgent: Agent + let aliceAgent: Agent let aliceConnection: ConnectionRecord beforeEach(async () => { diff --git a/tests/InMemoryStorageService.ts b/tests/InMemoryStorageService.ts index e1fc3f2f60..339080b404 100644 --- a/tests/InMemoryStorageService.ts +++ b/tests/InMemoryStorageService.ts @@ -18,7 +18,10 @@ interface StorageRecord { } @injectable() -export class InMemoryStorageService implements StorageService { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +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..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,9 +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 { agentDependencies } from '@aries-framework/node' - import { SubjectOutboundTransport } from './transport/SubjectOutboundTransport' // FIXME: Re-include in tests when Askar NodeJS wrapper performance is improved @@ -37,34 +35,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 +79,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 +92,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..04bc30ea17 100644 --- a/tests/e2e-http.test.ts +++ b/tests/e2e-http.test.ts @@ -1,3 +1,6 @@ +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' @@ -5,29 +8,45 @@ import { e2eTest } from './e2e-test' import { HttpOutboundTransport, Agent, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { HttpInboundTransport } from '@aries-framework/node' -const recipientAgentOptions = getAgentOptions('E2E HTTP Recipient', { - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const recipientAgentOptions = getAgentOptions( + 'E2E HTTP Recipient', + { + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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..6ec26ef417 100644 --- a/tests/e2e-subject.test.ts +++ b/tests/e2e-subject.test.ts @@ -1,35 +1,53 @@ 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' -const recipientAgentOptions = getAgentOptions('E2E Subject Recipient', { - 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, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) +const mediatorAgentOptions = getAgentOptions( + 'E2E Subject Mediator', + { + endpoints: ['rxjs:mediator'], + autoAcceptMediationRequests: true, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) +const senderAgentOptions = getAgentOptions( + 'E2E Subject Sender', + { + endpoints: ['rxjs:sender'], + mediatorPollingInterval: 1000, + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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..b1bee59014 100644 --- a/tests/e2e-test.ts +++ b/tests/e2e-test.ts @@ -1,19 +1,37 @@ -import type { Agent } from '@aries-framework/core' +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' +import { V1CredentialPreview } from '../packages/anoncreds/src/protocols/credentials/v1' +import { + issueLegacyAnonCredsCredential, + presentLegacyAnonCredsProof, + prepareForAnonCredsIssuance, +} from '../packages/anoncreds/tests/legacyAnonCredsSetup' import { sleep } from '../packages/core/src/utils/sleep' -import { issueCredential, makeConnection, prepareForIssuance, presentProof } from '../packages/core/tests/helpers' +import { setupEventReplaySubjects } from '../packages/core/tests' +import { makeConnection } from '../packages/core/tests/helpers' -import { V1CredentialPreview, CredentialState, MediationState, ProofState } from '@aries-framework/core' +import { + CredentialState, + MediationState, + ProofState, + CredentialEventTypes, + ProofEventTypes, +} from '@aries-framework/core' export async function e2eTest({ mediatorAgent, recipientAgent, senderAgent, }: { - mediatorAgent: Agent - recipientAgent: Agent - senderAgent: Agent + mediatorAgent: AnonCredsTestsAgent + recipientAgent: AnonCredsTestsAgent + senderAgent: AnonCredsTestsAgent }) { + const [senderReplay, recipientReplay] = setupEventReplaySubjects( + [senderAgent, recipientAgent], + [CredentialEventTypes.CredentialStateChanged, ProofEventTypes.ProofStateChanged] + ) + // Make connection between mediator and recipient const [mediatorRecipientConnection, recipientMediatorConnection] = await makeConnection(mediatorAgent, recipientAgent) expect(recipientMediatorConnection).toBeConnectedWith(mediatorRecipientConnection) @@ -33,13 +51,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 +74,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..16ace3cd90 100644 --- a/tests/e2e-ws-pickup-v2.test.ts +++ b/tests/e2e-ws-pickup-v2.test.ts @@ -1,34 +1,55 @@ +import type { AnonCredsTestsAgent } from '../packages/anoncreds/tests/legacyAnonCredsSetup' + +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 recipientOptions = getAgentOptions('E2E WS Pickup V2 Recipient ', { - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, -}) +const recipientOptions = getAgentOptions( + 'E2E WS Pickup V2 Recipient ', + { + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV2, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) // 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, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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..942ea92971 100644 --- a/tests/e2e-ws.test.ts +++ b/tests/e2e-ws.test.ts @@ -1,3 +1,6 @@ +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' @@ -5,29 +8,45 @@ import { e2eTest } from './e2e-test' import { Agent, WsOutboundTransport, AutoAcceptCredential, MediatorPickupStrategy } from '@aries-framework/core' import { WsInboundTransport } from '@aries-framework/node' -const recipientAgentOptions = getAgentOptions('E2E WS Recipient ', { - autoAcceptCredentials: AutoAcceptCredential.ContentApproved, - mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, -}) +const recipientAgentOptions = getAgentOptions( + 'E2E WS Recipient ', + { + mediatorPickupStrategy: MediatorPickupStrategy.PickUpV1, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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, + }, + getLegacyAnonCredsModules({ + autoAcceptCredentials: AutoAcceptCredential.ContentApproved, + }) +) 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) diff --git a/yarn.lock b/yarn.lock index 0a86c85b57..1c48eeb5a7 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" @@ -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== @@ -2534,10 +2534,10 @@ dependencies: "@types/node" "*" -"@types/indy-sdk-react-native@npm:@types/indy-sdk@1.16.24", "@types/indy-sdk@1.16.24": - version "1.16.24" - resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.24.tgz#1b8e33e8fd2a095a29cb06b76146ed14d1477cdd" - integrity sha512-5YliU8lqahihz46MPpiu1ZWNkG2c/lm9SI+Fp3DUV2HrGbuAPxI8dYg2CP6avuD5kfCYr6Y5+TaqeOH/aID0FQ== +"@types/indy-sdk@1.16.26", "@types/indy-sdk@^1.16.26": + version "1.16.26" + resolved "https://registry.yarnpkg.com/@types/indy-sdk/-/indy-sdk-1.16.26.tgz#871f82c3f7d241d649aff5eb6800048890efb8f8" + integrity sha512-KlnjsVsX/7yTmyyIlHWcytlBHoQ1vPGeiLnLv5y1vDftL6OQ5V+hebfAr7d3roMEsjCTH3qKkklwGcj1qS90YA== dependencies: buffer "^6.0.0" @@ -3534,7 +3534,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== @@ -3656,7 +3656,7 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -buffer@^6.0.0, buffer@^6.0.2, buffer@^6.0.3: +buffer@^6.0.0, buffer@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/buffer/-/buffer-6.0.3.tgz#2ace578459cc8fbe2a70aaa8f52ee63b6a74c6c6" integrity sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA== @@ -6178,14 +6178,7 @@ indent-string@^4.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== -indy-sdk-react-native@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/indy-sdk-react-native/-/indy-sdk-react-native-0.3.0.tgz#37b20476bf1207d3dea7b66dba65bf44ed0c903a" - integrity sha512-3qaB4R7QDNQRI9ijpSvMaow/HlZYMB2LdJlRtbhefmrjQYwpz9oSqB595NPKajBIoIxzgDaUdBkK7kmwMY90Xg== - 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== @@ -10029,7 +10022,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==