diff --git a/apps/shared/src/events/credentialOfferEvents.ts b/apps/shared/src/events/credentialOfferEvents.ts index 63e9eb15e81cc97ef0c9a264226b5c893e21a497..0735939c6bc3d82685beb704be49b9ef5d156095 100644 --- a/apps/shared/src/events/credentialOfferEvents.ts +++ b/apps/shared/src/events/credentialOfferEvents.ts @@ -1,5 +1,9 @@ import type { BaseEventInput } from './baseEvents.js'; -import type { CredentialExchangeRecord } from '@aries-framework/core'; + +import { + CredentialExchangeRecord, + JsonTransformer, +} from '@aries-framework/core'; import { BaseEvent } from './baseEvents.js'; @@ -11,7 +15,9 @@ export class EventAnonCredsCredentialOfferGetAll extends BaseEvent< public static token = 'anoncreds.credentialOffers.getAll'; public get instance() { - return this.data; + return this.data.map((d) => + JsonTransformer.fromJSON(d, CredentialExchangeRecord), + ); } public static fromEvent(e: EventAnonCredsCredentialOfferGetAll) { @@ -33,7 +39,9 @@ export class EventAnonCredsCredentialOfferGetById extends BaseEvent<CredentialEx public static token = 'anoncreds.credentialOffers.getById'; public get instance() { - return this.data; + return this.data + ? JsonTransformer.fromJSON(this.data, CredentialExchangeRecord) + : null; } public static fromEvent(e: EventAnonCredsCredentialOfferGetById) { diff --git a/apps/shared/src/events/credentialRequestEvents.ts b/apps/shared/src/events/credentialRequestEvents.ts index 9daefa1066c768641d71f5968464cd40ee9c9481..4039e655ee09cb08db7a23f8bc617f72fe9e487a 100644 --- a/apps/shared/src/events/credentialRequestEvents.ts +++ b/apps/shared/src/events/credentialRequestEvents.ts @@ -1,5 +1,9 @@ import type { BaseEventInput } from './baseEvents.js'; -import type { CredentialExchangeRecord } from '@aries-framework/core'; + +import { + CredentialExchangeRecord, + JsonTransformer, +} from '@aries-framework/core'; import { BaseEvent } from './baseEvents.js'; @@ -11,7 +15,9 @@ export class EventAnonCredsCredentialRequestGetAll extends BaseEvent< public static token = 'anoncreds.credentialRequests.getAll'; public get instance() { - return this.data; + return this.data.map((d) => + JsonTransformer.fromJSON(d, CredentialExchangeRecord), + ); } public static fromEvent(e: EventAnonCredsCredentialRequestGetAll) { @@ -33,7 +39,9 @@ export class EventAnonCredsCredentialRequestGetById extends BaseEvent<Credential public static token = 'anoncreds.credentialRequests.getById'; public get instance() { - return this.data; + return this.data + ? JsonTransformer.fromJSON(this.data, CredentialExchangeRecord) + : null; } public static fromEvent(e: EventAnonCredsCredentialRequestGetById) { diff --git a/apps/shared/src/events/didEvents.ts b/apps/shared/src/events/didEvents.ts index e300545be74213c4172a34a216ecdd7eaf50c21a..1baaa030e86f81641442837db0e45697a0255f98 100644 --- a/apps/shared/src/events/didEvents.ts +++ b/apps/shared/src/events/didEvents.ts @@ -19,6 +19,11 @@ export class EventDidsResolve extends BaseEvent<DidDocument> { export type EventDidsRegisterIndyFromSeedInput = BaseEventInput<{ seed: string; + services?: Array<{ + identifier: string; + url: string; + type: string; + }>; }>; export class EventDidsRegisterIndyFromSeed extends BaseEvent<Array<string>> { public static token = 'dids.register.indy.fromSeed'; diff --git a/apps/shared/src/events/proofEvents.ts b/apps/shared/src/events/proofEvents.ts new file mode 100644 index 0000000000000000000000000000000000000000..195777bd81c2776394480abb9a1223295476b71e --- /dev/null +++ b/apps/shared/src/events/proofEvents.ts @@ -0,0 +1,108 @@ +import type { BaseEventInput } from './baseEvents.js'; +import type { + AnonCredsPredicateType, + AnonCredsProofRequestRestriction, +} from '@aries-framework/anoncreds'; + +import { JsonTransformer, ProofExchangeRecord } from '@aries-framework/core'; + +import { BaseEvent } from './baseEvents.js'; + +export type EventAnonCredsProofsGetAllInput = BaseEventInput; +export class EventAnonCredsProofsGetAll extends BaseEvent< + Array<ProofExchangeRecord> +> { + public static token = 'anoncreds.proofs.getAll'; + + public get instance() { + return this.data.map((d) => + JsonTransformer.fromJSON(d, ProofExchangeRecord), + ); + } + + public static fromEvent(e: EventAnonCredsProofsGetAll) { + return new EventAnonCredsProofsGetAll( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventAnonCredsProofsGetByIdInput = BaseEventInput<{ + proofRecordId: string; +}>; +export class EventAnonCredsProofsGetById extends BaseEvent<ProofExchangeRecord | null> { + public static token = 'anoncreds.proofs.getById'; + + public get instance() { + return this.data + ? JsonTransformer.fromJSON(this.data, ProofExchangeRecord) + : this.data; + } + + public static fromEvent(e: EventAnonCredsProofsGetById) { + return new EventAnonCredsProofsGetById( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventDidcommAnonCredsProofsRequestInput = BaseEventInput<{ + connectionId: string; + name: string; + requestedAttributes: { + [groupName: string]: { + names: Array<string>; + restrictions?: Array<AnonCredsProofRequestRestriction>; + }; + }; + requestedPredicates: { + [groupName: string]: { + name: string; + predicateType: AnonCredsPredicateType; + predicateValue: number; + restrictions?: Array<AnonCredsProofRequestRestriction>; + }; + }; +}>; +export class EventDidcommAnonCredsProofsRequest extends BaseEvent<ProofExchangeRecord> { + public static token = 'didcomm.anoncreds.proofs.request'; + + public get instance() { + return JsonTransformer.fromJSON(this.data, ProofExchangeRecord); + } + + public static fromEvent(e: EventDidcommAnonCredsProofsRequest) { + return new EventDidcommAnonCredsProofsRequest( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} + +export type EventAnonCredsProofsDeleteByIdInput = BaseEventInput<{ + proofRecordId: string; +}>; +export class EventAnonCredsProofsDeleteById extends BaseEvent { + public static token = 'anoncreds.proofs.deleteById'; + + public static fromEvent(e: EventDidcommAnonCredsProofsRequest) { + return new EventDidcommAnonCredsProofsRequest( + e.data, + e.tenantId, + e.id, + e.type, + e.timestamp, + ); + } +} diff --git a/apps/shared/src/index.ts b/apps/shared/src/index.ts index 20c9faf786512da49c496a8d717ff643e430242b..36edf136298f8c16bbcaea2801132030521a0951 100644 --- a/apps/shared/src/index.ts +++ b/apps/shared/src/index.ts @@ -12,6 +12,7 @@ export * from './events/credentialDefinitionEvents.js'; export * from './events/credentialEvents.js'; export * from './events/credentialOfferEvents.js'; export * from './events/credentialRequestEvents.js'; +export * from './events/proofEvents.js'; export * from './dto/pagination-params.dto.js'; export * from './dto/multitenancy-params.dto.js'; diff --git a/apps/ssi-abstraction/src/agent/agent.service.ts b/apps/ssi-abstraction/src/agent/agent.service.ts index b1d9bd9279c2175eae5386fe50ecdccc14996a9e..47fe62fbe89e34e0c1893640f495513633dc2ee3 100644 --- a/apps/ssi-abstraction/src/agent/agent.service.ts +++ b/apps/ssi-abstraction/src/agent/agent.service.ts @@ -6,7 +6,9 @@ import type { OnApplicationShutdown } from '@nestjs/common'; import { AnonCredsCredentialFormatService, AnonCredsModule, + AnonCredsProofFormatService, LegacyIndyCredentialFormatService, + LegacyIndyProofFormatService, } from '@aries-framework/anoncreds'; import { AnonCredsRsModule } from '@aries-framework/anoncreds-rs'; import { AskarModule } from '@aries-framework/askar'; @@ -23,11 +25,14 @@ import { LogLevel, PeerDidRegistrar, PeerDidResolver, + ProofsModule, V2CredentialProtocol, + V2ProofProtocol, WebDidResolver, } from '@aries-framework/core'; import { IndyVdrAnonCredsRegistry, + IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrModule, IndyVdrSovDidResolver, @@ -90,13 +95,14 @@ export class AgentService implements OnApplicationShutdown { } public get modules() { - const { autoAcceptConnection, autoAcceptCredential } = + const { autoAcceptConnection, autoAcceptCredential, autoAcceptProof } = this.configService.get('agent'); return { connections: new ConnectionsModule({ autoAcceptConnections: autoAcceptConnection, }), + credentials: new CredentialsModule({ autoAcceptCredentials: autoAcceptCredential, credentialProtocols: [ @@ -109,6 +115,18 @@ export class AgentService implements OnApplicationShutdown { ], }), + proofs: new ProofsModule({ + autoAcceptProofs: autoAcceptProof, + proofProtocols: [ + new V2ProofProtocol({ + proofFormats: [ + new AnonCredsProofFormatService(), + new LegacyIndyProofFormatService(), + ], + }), + ], + }), + anoncredsRs: new AnonCredsRsModule({ anoncreds }), anoncreds: new AnonCredsModule({ registries: [new IndyVdrAnonCredsRegistry()], @@ -128,6 +146,7 @@ export class AgentService implements OnApplicationShutdown { new PeerDidRegistrar(), new KeyDidRegistrar(), new JwkDidRegistrar(), + new IndyVdrIndyDidRegistrar() ], }), diff --git a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts index a434e597d68e243499ef5ff02e0f9bf3aa8ef1a5..af6a9933d3a77ff8a817ff679369b593e05dc9d9 100644 --- a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts +++ b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.controller.ts @@ -1,24 +1,20 @@ import { Controller } from '@nestjs/common'; import { MessagePattern } from '@nestjs/microservices'; import { + EventAnonCredsCredentialOfferGetById, + EventAnonCredsCredentialOfferGetByIdInput, + EventAnonCredsCredentialRequestGetById, + EventAnonCredsCredentialRequestGetByIdInput, + EventAnonCredsCredentialsDeleteById, + EventAnonCredsCredentialsDeleteByIdInput, EventAnonCredsCredentialsGetAll, EventAnonCredsCredentialsGetAllInput, EventAnonCredsCredentialsGetById, EventAnonCredsCredentialsGetByIdInput, EventDidcommAnonCredsCredentialsOffer, EventDidcommAnonCredsCredentialsOfferInput, - EventDidcommAnonCredsCredentialsOfferToSelfInput, EventDidcommAnonCredsCredentialsOfferToSelf, - EventAnonCredsCredentialOfferGetAll, - EventAnonCredsCredentialOfferGetAllInput, - EventAnonCredsCredentialRequestGetAll, - EventAnonCredsCredentialRequestGetAllInput, - EventAnonCredsCredentialsDeleteById, - EventAnonCredsCredentialsDeleteByIdInput, - EventAnonCredsCredentialOfferGetById, - EventAnonCredsCredentialOfferGetByIdInput, - EventAnonCredsCredentialRequestGetById, - EventAnonCredsCredentialRequestGetByIdInput, + EventDidcommAnonCredsCredentialsOfferToSelfInput } from '@ocm/shared'; import { AnonCredsCredentialsService } from './anoncredsCredentials.service.js'; @@ -37,26 +33,6 @@ export class AnonCredsCredentialsController { ); } - @MessagePattern(EventAnonCredsCredentialOfferGetAll.token) - public async getAllOffers( - options: EventAnonCredsCredentialOfferGetAllInput, - ): Promise<EventAnonCredsCredentialOfferGetAll> { - return new EventAnonCredsCredentialOfferGetAll( - await this.credentialsService.getAllOffers(options), - options.tenantId, - ); - } - - @MessagePattern(EventAnonCredsCredentialRequestGetAll.token) - public async getAllRequests( - options: EventAnonCredsCredentialRequestGetAllInput, - ): Promise<EventAnonCredsCredentialRequestGetAll> { - return new EventAnonCredsCredentialRequestGetAll( - await this.credentialsService.getAllRequests(options), - options.tenantId, - ); - } - @MessagePattern(EventAnonCredsCredentialsGetById.token) public async getById( options: EventAnonCredsCredentialsGetByIdInput, diff --git a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts index a1aa04b633c5bb454e2cb7e72b81f4935e48d586..94617f16d8574e662cbbc55699b4e07aaa2f0a8a 100644 --- a/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts +++ b/apps/ssi-abstraction/src/agent/anoncredsCredentials/anoncredsCredentials.service.ts @@ -1,22 +1,21 @@ +import type { CredentialExchangeRecord} from '@aries-framework/core'; import type { - EventAnonCredsCredentialRequestGetByIdInput, + EventAnonCredsCredentialOfferGetAll, EventAnonCredsCredentialOfferGetAllInput, EventAnonCredsCredentialOfferGetById, EventAnonCredsCredentialOfferGetByIdInput, + EventAnonCredsCredentialRequestGetAll, EventAnonCredsCredentialRequestGetAllInput, + EventAnonCredsCredentialRequestGetById, + EventAnonCredsCredentialRequestGetByIdInput, + EventAnonCredsCredentialsDeleteById, + EventAnonCredsCredentialsDeleteByIdInput, EventAnonCredsCredentialsGetAllInput, EventAnonCredsCredentialsGetByIdInput, - EventDidcommAnonCredsCredentialsOfferInput, - EventDidcommAnonCredsCredentialsOfferToSelfInput, - EventAnonCredsCredentialRequestGetById, EventDidcommAnonCredsCredentialsOffer, + EventDidcommAnonCredsCredentialsOfferInput, EventDidcommAnonCredsCredentialsOfferToSelf, - EventAnonCredsCredentialsGetById, - EventAnonCredsCredentialRequestGetAll, - EventAnonCredsCredentialOfferGetAll, - EventAnonCredsCredentialsGetAll, - EventAnonCredsCredentialsDeleteByIdInput, - EventAnonCredsCredentialsDeleteById, + EventDidcommAnonCredsCredentialsOfferToSelfInput } from '@ocm/shared'; import { AutoAcceptCredential, CredentialState } from '@aries-framework/core'; @@ -33,7 +32,7 @@ export class AnonCredsCredentialsService { public async getAll({ tenantId, }: EventAnonCredsCredentialsGetAllInput): Promise< - EventAnonCredsCredentialsGetAll['data'] + Array<CredentialExchangeRecord> > { return this.withTenantService.invoke(tenantId, (t) => t.credentials.getAll(), @@ -85,9 +84,7 @@ export class AnonCredsCredentialsService { public async getById({ tenantId, credentialRecordId, - }: EventAnonCredsCredentialsGetByIdInput): Promise< - EventAnonCredsCredentialsGetById['data'] - > { + }: EventAnonCredsCredentialsGetByIdInput): Promise<CredentialExchangeRecord | null> { return this.withTenantService.invoke(tenantId, (t) => t.credentials.findById(credentialRecordId), ); diff --git a/apps/ssi-abstraction/src/agent/anoncredsProofs/__tests__/anoncredsProofs.controller.spec.ts b/apps/ssi-abstraction/src/agent/anoncredsProofs/__tests__/anoncredsProofs.controller.spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..91eb60562a79e398f61630432b4c6628ea053a40 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/anoncredsProofs/__tests__/anoncredsProofs.controller.spec.ts @@ -0,0 +1,77 @@ +import { ProofExchangeRecord, ProofState } from '@aries-framework/core'; +import { Test } from '@nestjs/testing'; + +import { mockConfigModule } from '../../../config/__tests__/mockConfig.js'; +import { AgentModule } from '../../agent.module.js'; +import { AnonCredsProofsController } from '../anoncredsProofs.controller.js'; +import { AnonCredsProofsService } from '../anoncredsProofs.service.js'; + +describe('AnonCredsProofsController', () => { + let proofsController: AnonCredsProofsController; + let proofsService: AnonCredsProofsService; + + beforeEach(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [mockConfigModule(), AgentModule], + controllers: [AnonCredsProofsController], + providers: [AnonCredsProofsService], + }).compile(); + + proofsService = moduleRef.get(AnonCredsProofsService); + proofsController = moduleRef.get(AnonCredsProofsController); + }); + + it('get all', async () => { + const result: Array<ProofExchangeRecord> = []; + jest.spyOn(proofsService, 'getAll').mockResolvedValue(result); + + const event = await proofsController.getAll({ + tenantId: 'some-id', + }); + + expect(event.data).toStrictEqual(result); + }); + + it('get by id', async () => { + const result: ProofExchangeRecord | null = null; + jest.spyOn(proofsService, 'getById').mockResolvedValue(result); + + const event = await proofsController.getById({ + tenantId: 'some-id', + proofRecordId: 'some-id', + }); + + expect(event.data).toStrictEqual(result); + }); + + it('request', async () => { + const result = new ProofExchangeRecord({ + state: ProofState.Done, + threadId: 'some-id', + protocolVersion: 'v2', + }); + jest.spyOn(proofsService, 'request').mockResolvedValue(result); + + const event = await proofsController.request({ + tenantId: 'some-id', + connectionId: 'some-id', + name: 'My New Proof Request', + requestedAttributes: { + identity: { + names: ['name'], + restrictions: [{ issuer_id: 'did:web:government.org' }], + }, + }, + requestedPredicates: { + 'age > 18': { + name: 'age', + restrictions: [{ issuer_id: 'did:web:government.org' }], + predicateType: '>', + predicateValue: 18, + }, + }, + }); + + expect(event.data).toStrictEqual(result); + }); +}); diff --git a/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.controller.ts b/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.controller.ts new file mode 100644 index 0000000000000000000000000000000000000000..c29a03defeba3e94702a26aac505c0fe3bb3b7e0 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.controller.ts @@ -0,0 +1,59 @@ +import { Controller } from '@nestjs/common'; +import { MessagePattern } from '@nestjs/microservices'; +import { + EventAnonCredsProofsDeleteById, + EventAnonCredsProofsDeleteByIdInput, + EventAnonCredsProofsGetAll, + EventAnonCredsProofsGetAllInput, + EventAnonCredsProofsGetById, + EventAnonCredsProofsGetByIdInput, + EventDidcommAnonCredsProofsRequest, + EventDidcommAnonCredsProofsRequestInput, +} from '@ocm/shared'; + +import { AnonCredsProofsService } from './anoncredsProofs.service.js'; + +@Controller('anoncredsProofs') +export class AnonCredsProofsController { + public constructor(private proofsService: AnonCredsProofsService) {} + + @MessagePattern(EventAnonCredsProofsGetAll.token) + public async getAll( + options: EventAnonCredsProofsGetAllInput, + ): Promise<EventAnonCredsProofsGetAll> { + return new EventAnonCredsProofsGetAll( + await this.proofsService.getAll(options), + options.tenantId, + ); + } + + @MessagePattern(EventAnonCredsProofsGetById.token) + public async getById( + options: EventAnonCredsProofsGetByIdInput, + ): Promise<EventAnonCredsProofsGetById> { + return new EventAnonCredsProofsGetById( + await this.proofsService.getById(options), + options.tenantId, + ); + } + + @MessagePattern(EventAnonCredsProofsDeleteById.token) + public async deleteById( + options: EventAnonCredsProofsDeleteByIdInput, + ): Promise<EventAnonCredsProofsDeleteById> { + return new EventAnonCredsProofsDeleteById( + await this.proofsService.deleteById(options), + options.tenantId, + ); + } + + @MessagePattern(EventDidcommAnonCredsProofsRequest.token) + public async request( + options: EventDidcommAnonCredsProofsRequestInput, + ): Promise<EventDidcommAnonCredsProofsRequest> { + return new EventDidcommAnonCredsProofsRequest( + await this.proofsService.request(options), + options.tenantId, + ); + } +} diff --git a/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.module.ts b/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.module.ts new file mode 100644 index 0000000000000000000000000000000000000000..5ab1719ba2aff98122bf50ede7538dde777c3615 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common'; + +import { AgentModule } from '../agent.module.js'; + +import { AnonCredsProofsController } from './anoncredsProofs.controller.js'; +import { AnonCredsProofsService } from './anoncredsProofs.service.js'; + +@Module({ + imports: [AgentModule], + providers: [AnonCredsProofsService], + controllers: [AnonCredsProofsController], +}) +export class AnonCredsProofsModule {} diff --git a/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.service.ts b/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.service.ts new file mode 100644 index 0000000000000000000000000000000000000000..6de76335a02b03d4221795d498357d7d615eb4a5 --- /dev/null +++ b/apps/ssi-abstraction/src/agent/anoncredsProofs/anoncredsProofs.service.ts @@ -0,0 +1,88 @@ +import type { + EventAnonCredsProofsDeleteById, + EventAnonCredsProofsDeleteByIdInput, + EventAnonCredsProofsGetAll, + EventAnonCredsProofsGetAllInput, + EventAnonCredsProofsGetById, + EventAnonCredsProofsGetByIdInput, + EventDidcommAnonCredsProofsRequest, + EventDidcommAnonCredsProofsRequestInput, +} from '@ocm/shared'; + +import { Injectable } from '@nestjs/common'; + +import { WithTenantService } from '../withTenantService.js'; + +@Injectable() +export class AnonCredsProofsService { + public constructor(private withTenantService: WithTenantService) {} + + public async getAll({ + tenantId, + }: EventAnonCredsProofsGetAllInput): Promise< + EventAnonCredsProofsGetAll['data'] + > { + return this.withTenantService.invoke(tenantId, (t) => t.proofs.getAll()); + } + + public async getById({ + tenantId, + proofRecordId, + }: EventAnonCredsProofsGetByIdInput): Promise< + EventAnonCredsProofsGetById['data'] + > { + return this.withTenantService.invoke(tenantId, (t) => + t.proofs.findById(proofRecordId), + ); + } + + public async deleteById({ + tenantId, + proofRecordId, + }: EventAnonCredsProofsDeleteByIdInput): Promise< + EventAnonCredsProofsDeleteById['data'] + > { + return this.withTenantService.invoke(tenantId, async (t) => { + await t.proofs.deleteById(proofRecordId); + return {}; + }); + } + + public async request({ + tenantId, + connectionId, + name, + requestedAttributes, + requestedPredicates, + }: EventDidcommAnonCredsProofsRequestInput): Promise< + EventDidcommAnonCredsProofsRequest['data'] + > { + const transformedPredicates = Object.entries(requestedPredicates).reduce( + (prev, [key, value]) => ({ + ...prev, + [key]: { + name: value.name, + restrictions: value.restrictions, + p_type: value.predicateType, + p_value: value.predicateValue, + }, + }), + {}, + ); + + return this.withTenantService.invoke(tenantId, (t) => + t.proofs.requestProof({ + connectionId, + protocolVersion: 'v2', + proofFormats: { + anoncreds: { + name, + version: '1.0', + requested_attributes: requestedAttributes, + requested_predicates: transformedPredicates, + }, + }, + }), + ); + } +} diff --git a/apps/ssi-abstraction/src/agent/dids/dids.service.ts b/apps/ssi-abstraction/src/agent/dids/dids.service.ts index 7b0ad1ec1682b95964a3e21d9ae619bca4787478..76cd0a8b0a8f1b0ae1f623cc704502774485032c 100644 --- a/apps/ssi-abstraction/src/agent/dids/dids.service.ts +++ b/apps/ssi-abstraction/src/agent/dids/dids.service.ts @@ -1,6 +1,21 @@ -import type { EventDidsResolveInput } from '@ocm/shared'; +import type { LEDGERS } from '../../config/ledger.js'; +import type { + IndyVdrDidCreateOptions, + IndyVdrDidCreateResult, +} from '@aries-framework/indy-vdr'; +import type { + EventDidsRegisterIndyFromSeed, + EventDidsRegisterIndyFromSeedInput, + EventDidsResolve, + EventDidsResolveInput, +} from '@ocm/shared'; -import { KeyType, TypedArrayEncoder } from '@aries-framework/core'; +import { + DidDocumentService, + Hasher, + KeyType, + TypedArrayEncoder, +} from '@aries-framework/core'; import { Injectable } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; @@ -14,7 +29,10 @@ export class DidsService { private configService: ConfigService, ) {} - public async resolve({ did, tenantId }: EventDidsResolveInput) { + public async resolve({ + did, + tenantId, + }: EventDidsResolveInput): Promise<EventDidsResolve['data']> { return this.withTenantService.invoke(tenantId, async (t) => { const { didDocument, @@ -36,32 +54,80 @@ export class DidsService { public async registerDidIndyFromSeed({ tenantId, seed, - }: { - tenantId: string; - seed: string; - }): Promise<Array<string>> { - const ledgerIds = this.configService.get('agent.ledgerIds'); + services, + }: EventDidsRegisterIndyFromSeedInput): Promise< + EventDidsRegisterIndyFromSeed['data'] + > { + const dids: Array<string> = []; + + const ledgerIds = this.configService.get('agent.ledgerIds') as Array< + keyof typeof LEDGERS + >; + + const publicDidSeed = this.configService.get( + 'agent.publicDidSeed', + ) as string; - const registeredPublicDidResponses = await registerPublicDids({ + const publicDids = await registerPublicDids({ ledgerIds, - seed, + seed: publicDidSeed, }); - for (const publicDidResponse of registeredPublicDidResponses) { - await this.withTenantService.invoke(tenantId, (t) => - t.dids.import({ - overwrite: true, - did: publicDidResponse.did, - privateKeys: [ - { - keyType: KeyType.Ed25519, - privateKey: TypedArrayEncoder.fromString(seed), - }, - ], + const { publicKey, publicKeyBase58 } = await this.withTenantService.invoke( + tenantId, + async (t) => + t.wallet.createKey({ + privateKey: TypedArrayEncoder.fromString(seed), + keyType: KeyType.Ed25519, }), - ); + ); + + await this.withTenantService.invoke(tenantId, async (t) => + t.wallet.createKey({ + privateKey: TypedArrayEncoder.fromString(publicDidSeed), + keyType: KeyType.Ed25519, + }), + ); + + const buffer = Hasher.hash(publicKey, 'sha2-256'); + const id = TypedArrayEncoder.toBase58(buffer.slice(0, 16)); + const verkey = publicKeyBase58; + + for (const publicDid of publicDids) { + const did = `did:indy:${publicDid.namespace}:${id}`; + const didDocumentServices: Array<DidDocumentService> | undefined = + services?.map( + (s) => + new DidDocumentService({ + id: `${did}#${s.identifier}`, + type: s.type, + serviceEndpoint: s.url, + }), + ); + + + await this.withTenantService.invoke(tenantId, async (t) => { + const result = (await t.dids.create<IndyVdrDidCreateOptions>({ + did, + options: { + verkey, + endorserMode: 'internal', + endorserDid: publicDid.did, + services: didDocumentServices, + useEndpointAttrib: true, + }, + })) as IndyVdrDidCreateResult; + + if (result.didState.state !== 'finished') { + throw Error( + `An error occurred while trying to register the did: '${did}'. Result: ${JSON.stringify(result)}`, + ); + } + + dids.push(result.didState.did); + }); } - return registeredPublicDidResponses.map((r) => r.did); + return dids; } } diff --git a/apps/ssi-abstraction/src/agent/ledger/register.ts b/apps/ssi-abstraction/src/agent/ledger/register.ts index 8600a454b48e609e2798aba6482cbd719720c419..b7127ff680ee0fd031b657a3a16167602bab3bd7 100644 --- a/apps/ssi-abstraction/src/agent/ledger/register.ts +++ b/apps/ssi-abstraction/src/agent/ledger/register.ts @@ -11,14 +11,17 @@ type RegisterPublicDidOptions = { }; type LedgerRegistrationBody = { - role?: 'ENDORSER'; - seed: string; + role: 'ENDORSER'; + seed?: string; + did?: string; + verkey?: string; }; type RegisterPublicDidResponse = { seed: string; did: string; verkey: string; + namespace: string; }; export const registerPublicDids = async ({ @@ -45,7 +48,7 @@ export const registerPublicDids = async ({ if (res.data) { logger.info('Agent DID registered.'); res.data.did = `did:indy:${ledgerNamespace}:${res.data.did}`; - responses.push(res.data); + responses.push({ ...res.data, namespace: ledgerNamespace }); } else { throw new Error('No data was returned from the ledger request'); } diff --git a/apps/ssi-abstraction/test/anoncredsProofs.e2e-spec.ts b/apps/ssi-abstraction/test/anoncredsProofs.e2e-spec.ts new file mode 100644 index 0000000000000000000000000000000000000000..fe6c5f7bf768c38d421e5514558879d4f8e8b625 --- /dev/null +++ b/apps/ssi-abstraction/test/anoncredsProofs.e2e-spec.ts @@ -0,0 +1,133 @@ +import type { INestApplication } from '@nestjs/common'; +import type { ClientProxy } from '@nestjs/microservices'; +import type { + EventAnonCredsProofsGetAllInput, + EventAnonCredsProofsGetByIdInput, + EventDidcommAnonCredsProofsRequestInput, +} from '@ocm/shared'; + +import { ProofState } from '@aries-framework/core'; +import { ClientsModule, Transport } from '@nestjs/microservices'; +import { Test } from '@nestjs/testing'; +import { + EventAnonCredsProofsGetAll, + EventAnonCredsProofsGetById, + EventDidcommAnonCredsProofsRequest, +} from '@ocm/shared'; +import { firstValueFrom } from 'rxjs'; + +import { AgentModule } from '../src/agent/agent.module.js'; +import { AnonCredsProofsModule } from '../src/agent/anoncredsProofs/anoncredsProofs.module.js'; +import { ConnectionsModule } from '../src/agent/connections/connections.module.js'; +import { ConnectionsService } from '../src/agent/connections/connections.service.js'; +import { DidsModule } from '../src/agent/dids/dids.module.js'; +import { TenantsModule } from '../src/agent/tenants/tenants.module.js'; +import { TenantsService } from '../src/agent/tenants/tenants.service.js'; +import { mockConfigModule } from '../src/config/__tests__/mockConfig.js'; + +describe('Proofs', () => { + const TOKEN = 'PROOFS_CLIENT_SERVICE'; + let app: INestApplication; + let client: ClientProxy; + let tenantId: string; + + let connectionId: string; + let credentialDefinitionId: string; + + beforeAll(async () => { + const moduleRef = await Test.createTestingModule({ + imports: [ + mockConfigModule(3004, true), + AgentModule, + ConnectionsModule, + AnonCredsProofsModule, + TenantsModule, + DidsModule, + ClientsModule.register([{ name: TOKEN, transport: Transport.NATS }]), + ], + }).compile(); + + app = moduleRef.createNestApplication(); + + app.connectMicroservice({ transport: Transport.NATS }); + + await app.startAllMicroservices(); + await app.init(); + + client = app.get(TOKEN); + await client.connect(); + + const tenantsService = app.get(TenantsService); + const { id: tId } = await tenantsService.create(TOKEN); + tenantId = tId; + + const connectionsService = app.get(ConnectionsService); + const { id } = await connectionsService.createConnectionWithSelf({ + tenantId, + }); + connectionId = id; + }); + + afterAll(async () => { + await app.close(); + client.close(); + }); + + it(EventAnonCredsProofsGetAll.token, async () => { + const response$ = client.send< + EventAnonCredsProofsGetAll, + EventAnonCredsProofsGetAllInput + >(EventAnonCredsProofsGetAll.token, { tenantId }); + const response = await firstValueFrom(response$); + const eventInstance = EventAnonCredsProofsGetAll.fromEvent(response); + + expect(eventInstance.instance).toEqual(expect.arrayContaining([])); + }); + + it(EventAnonCredsProofsGetById.token, async () => { + const response$ = client.send< + EventAnonCredsProofsGetById, + EventAnonCredsProofsGetByIdInput + >(EventAnonCredsProofsGetById.token, { + tenantId, + proofRecordId: 'some-id', + }); + const response = await firstValueFrom(response$); + const eventInstance = EventAnonCredsProofsGetById.fromEvent(response); + + expect(eventInstance.instance).toEqual(null); + }); + + it(EventDidcommAnonCredsProofsRequest.token, async () => { + const response$ = client.send< + EventDidcommAnonCredsProofsRequest, + EventDidcommAnonCredsProofsRequestInput + >(EventDidcommAnonCredsProofsRequest.token, { + tenantId, + name: 'My Test Proof Request', + connectionId, + requestedAttributes: { + Identity: { + names: ['Name'], + restrictions: [{ cred_def_id: credentialDefinitionId }], + }, + }, + requestedPredicates: { + 'Age > 21': { + name: 'Age', + restrictions: [{ cred_def_id: credentialDefinitionId }], + predicateType: '>', + predicateValue: 21, + }, + }, + }); + + const response = await firstValueFrom(response$); + const eventInstance = + EventDidcommAnonCredsProofsRequest.fromEvent(response); + + expect(eventInstance.instance).toMatchObject({ + state: ProofState.RequestSent, + }); + }); +}); diff --git a/apps/ssi-abstraction/test/dids.e2e-spec.ts b/apps/ssi-abstraction/test/dids.e2e-spec.ts index 52c8a7b66caaba8d59e7d2c64d98ce725e977532..2cbb580fd7142d239214c5338c9b0ff33ef5a63b 100644 --- a/apps/ssi-abstraction/test/dids.e2e-spec.ts +++ b/apps/ssi-abstraction/test/dids.e2e-spec.ts @@ -8,6 +8,7 @@ import type { import { ClientsModule, Transport } from '@nestjs/microservices'; import { Test } from '@nestjs/testing'; import { EventDidsRegisterIndyFromSeed, EventDidsResolve } from '@ocm/shared'; +import { randomBytes } from 'crypto'; import { firstValueFrom } from 'rxjs'; import { AgentModule } from '../src/agent/agent.module.js'; @@ -58,16 +59,23 @@ describe('Dids', () => { EventDidsRegisterIndyFromSeed, EventDidsRegisterIndyFromSeedInput >(EventDidsRegisterIndyFromSeed.token, { - seed: '12312367897123300000000000000000', + seed: randomBytes(16).toString('hex'), tenantId, + services: [ + { + url: 'https://example.org', + type: 'endpoint', + identifier: 'endpoint', + }, + ], }); const response = await firstValueFrom(response$); const eventInstance = EventDidsRegisterIndyFromSeed.fromEvent(response); - expect(eventInstance.instance).toMatchObject( - expect.arrayContaining(['did:indy:bcovrin:test:9MMeff63VnCpogD2FWfKnJ']), - ); + expect( + eventInstance.instance[0].startsWith('did:indy:bcovrin:test:'), + ).toBeTruthy(); }); it(EventDidsResolve.token, async () => {