diff --git a/apps/ssi-abstraction/src/agent/revocation/revocation.service.ts b/apps/ssi-abstraction/src/agent/revocation/revocation.service.ts index b223769289798cb5b5425efe7991ec1776d20346..26506a7355558e8eef0afc1fa8162da9debb20fb 100644 --- a/apps/ssi-abstraction/src/agent/revocation/revocation.service.ts +++ b/apps/ssi-abstraction/src/agent/revocation/revocation.service.ts @@ -11,6 +11,13 @@ import { Injectable } from '@nestjs/common'; import { WithTenantService } from '../withTenantService.js'; +export interface AnonCredsCredentialMetadata { + schemaId?: string; + credentialDefinitionId?: string; + revocationRegistryId?: string; + credentialRevocationId?: string; +} + @Injectable() export class RevocationService { public constructor(private withTenantService: WithTenantService) {} @@ -22,30 +29,156 @@ export class RevocationService { // Update the status list with the revoked index set public async revoke({ tenantId, + credentialId, }: EventAnonCredsRevocationRevokeInput): Promise< EventAnonCredsRevocationRevoke['data'] > { // eslint-disable-next-line @typescript-eslint/no-unused-vars - return this.withTenantService.invoke(tenantId, async (_) => ({})); + return this.withTenantService.invoke(tenantId, async (t) => { + const credential = await t.credentials.getById(credentialId); + + const metadata = credential.metadata.get<AnonCredsCredentialMetadata>( + '_anoncreds/credential', + ); + + if ( + !metadata || + !metadata.revocationRegistryId || + !metadata.credentialRevocationId + ) { + throw new Error( + `credential (${credentialId}) has no metadata, likley it was issued without support for revocation`, + ); + } + + const { revocationRegistryDefinition } = + await t.modules.anoncreds.getRevocationRegistryDefinition( + metadata.revocationRegistryId, + ); + + if (!revocationRegistryDefinition) { + throw new Error( + `Could not find the revocation registry definition for id: ${metadata.revocationRegistryId}`, + ); + } + + const timestamp = new Date().getTime() / 1000; + const { revocationStatusList } = + await t.modules.anoncreds.getRevocationStatusList( + metadata.revocationRegistryId, + timestamp, + ); + + if (!revocationStatusList) { + throw new Error( + `Could not find the revocation status list for revocation registry definition id: ${metadata.revocationRegistryId} and timestamp: ${timestamp}`, + ); + } + + if ( + revocationStatusList.revocationList[ + Number(metadata.credentialRevocationId) + ] === 1 + ) { + throw new Error( + `credential (${credentialId}), with revocation id ${metadata.credentialRevocationId}, is already in a revoked state`, + ); + } + + const newRevokedIndices = revocationStatusList.revocationList + .filter( + (state, idx) => + state === 1 || idx === Number(metadata.credentialRevocationId), + ) + .map((_, idx) => idx); + + const newIssuedIndices = revocationStatusList.revocationList + .filter( + (state, idx) => + state === 0 && idx !== Number(metadata.credentialRevocationId), + ) + .map((_, idx) => idx); + + const result = await t.modules.anoncreds.updateRevocationStatusList({ + revocationRegistryDefinitionId: metadata.revocationRegistryId, + revokedCredentialIndexes: newRevokedIndices, + issuedCredentialIndexes: newIssuedIndices, + }); + + if (result.revocationStatusListState.state !== 'finished') { + throw new Error( + `An error occurred while trying to update the revocation status list. Error: ${JSON.stringify( + result, + )}`, + ); + } + + return {}; + }); } public async registerRevocationRegistryDefinition({ tenantId, + tag, + issuerDid, + credentialDefinitionId, + maximumCredentialNumber, }: EventAnonCredsRevocationRegisterRevocationRegistryDefinitionInput): Promise< EventAnonCredsRevocationRegisterRevocationRegistryDefinition['data'] > { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - return this.withTenantService.invoke(tenantId, async (_) => ({ - revocationRegistryDefinitionId: 'TODO', - })); + return this.withTenantService.invoke(tenantId, async (t) => { + const result = + await t.modules.anoncreds.registerRevocationRegistryDefinition({ + options: {}, + revocationRegistryDefinition: { + maximumCredentialNumber, + credentialDefinitionId, + tag, + issuerId: issuerDid, + }, + }); + + if (result.revocationRegistryDefinitionState.state !== 'finished') { + throw new Error( + `Error registering the revocation registry definition. Error: ${JSON.stringify( + result, + )}`, + ); + } + + return { + revocationRegistryDefinitionId: + result.revocationRegistryDefinitionState + .revocationRegistryDefinitionId, + }; + }); } public async registerRevocationStatusList({ tenantId, + revocationRegistryDefinitionId, + issuerDid, }: EventAnonCredsRevocationRegisterRevocationStatusListInput): Promise< EventAnonCredsRevocationRegisterRevocationStatusList['data'] > { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - return this.withTenantService.invoke(tenantId, async (_) => ({})); + return this.withTenantService.invoke(tenantId, async (t) => { + const result = await t.modules.anoncreds.registerRevocationStatusList({ + options: {}, + revocationStatusList: { + revocationRegistryDefinitionId, + issuerId: issuerDid, + }, + }); + + if (result.revocationStatusListState.state !== 'finished') { + throw new Error( + `Error registering the revocation status list. Error: ${JSON.stringify( + result, + )}`, + ); + } + + return {}; + }); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1ba6ca8eb681355e4d853c15948f39fedef0327c..2ec7d810d27fa39e3e10e24a74daa0897f4823a5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4615,7 +4615,7 @@ packages: dependencies: '@nestjs/axios': 3.0.1(@nestjs/common@10.3.0)(axios@1.6.5)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/common': 10.3.0(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.1.14)(rxjs@7.8.1) - '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(@nestjs/platform-express@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) + '@nestjs/core': 10.3.0(@nestjs/common@10.3.0)(@nestjs/microservices@10.3.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) '@nestjs/microservices': 10.3.0(@nestjs/common@10.3.0)(@nestjs/core@10.3.0)(nats@2.19.0)(reflect-metadata@0.1.14)(rxjs@7.8.1) boxen: 5.1.2 check-disk-space: 3.4.0