From 3a4c5ecd940e49d4d192eef1d41f2aaedb34d85a Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 6 Feb 2023 21:49:12 +0100 Subject: [PATCH 1/2] feat(anoncreds): add anoncreds API (#1232) Signed-off-by: Timo Glastra --- packages/anoncreds/src/AnonCredsApi.ts | 428 ++++++++++++++++++ packages/anoncreds/src/AnonCredsApiOptions.ts | 4 + packages/anoncreds/src/AnonCredsModule.ts | 16 + .../src/__tests__/AnonCredsModule.test.ts | 14 +- .../src/error/AnonCredsStoreRecordError.ts | 7 + packages/anoncreds/src/error/index.ts | 1 + .../LegacyIndyCredentialFormatService.test.ts | 2 +- packages/anoncreds/src/index.ts | 4 + packages/anoncreds/src/models/exchange.ts | 2 +- packages/anoncreds/src/models/registry.ts | 2 +- ...nCredsCredentialDefinitionPrivateRecord.ts | 41 ++ ...dsCredentialDefinitionPrivateRepository.ts | 23 + .../AnonCredsCredentialDefinitionRecord.ts | 50 ++ ...AnonCredsCredentialDefinitionRepository.ts | 23 + .../AnonCredsKeyCorrectnessProofRecord.ts | 41 ++ .../AnonCredsKeyCorrectnessProofRepository.ts | 23 + .../repository/AnonCredsLinkSecretRecord.ts | 42 ++ .../AnonCredsLinkSecretRepository.ts | 31 ++ .../src/repository/AnonCredsSchemaRecord.ts | 50 ++ .../repository/AnonCredsSchemaRepository.ts | 23 + ...CredentialDefinitionRecordMetadataTypes.ts | 11 + .../anonCredsSchemaRecordMetadataTypes.ts | 11 + packages/anoncreds/src/repository/index.ts | 10 + .../src/services/AnonCredsHolderService.ts | 4 + .../services/AnonCredsHolderServiceOptions.ts | 16 +- .../src/services/AnonCredsIssuerService.ts | 5 +- .../services/AnonCredsIssuerServiceOptions.ts | 8 +- .../AnonCredsVerifierServiceOptions.ts | 6 +- .../services/registry/AnonCredsRegistry.ts | 7 +- .../registry/AnonCredsRegistryService.ts | 2 +- ...ions.ts => RevocationStatusListOptions.ts} | 8 +- .../AnonCredsRegistryService.test.ts | 2 +- .../anoncreds/src/services/registry/index.ts | 2 +- .../tests/InMemoryAnonCredsRegistry.ts | 83 +++- packages/anoncreds/tests/anoncreds.test.ts | 312 +++++++++++++ packages/anoncreds/tests/setup.ts | 2 +- packages/indy-sdk/src/IndySdkModule.ts | 18 + .../services/IndySdkAnonCredsRegistry.ts | 16 +- .../services/IndySdkHolderService.ts | 30 +- .../services/IndySdkIssuerService.ts | 19 +- .../services/IndySdkRevocationService.ts | 18 +- .../services/IndySdkVerifierService.ts | 11 +- .../utils/__tests__/transform.test.ts | 2 +- .../indy-sdk/src/anoncreds/utils/transform.ts | 20 +- 44 files changed, 1370 insertions(+), 80 deletions(-) create mode 100644 packages/anoncreds/src/AnonCredsApi.ts create mode 100644 packages/anoncreds/src/AnonCredsApiOptions.ts create mode 100644 packages/anoncreds/src/error/AnonCredsStoreRecordError.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRecord.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRepository.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRecord.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRepository.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsLinkSecretRecord.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsLinkSecretRepository.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsSchemaRecord.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsSchemaRepository.ts create mode 100644 packages/anoncreds/src/repository/anonCredsCredentialDefinitionRecordMetadataTypes.ts create mode 100644 packages/anoncreds/src/repository/anonCredsSchemaRecordMetadataTypes.ts create mode 100644 packages/anoncreds/src/repository/index.ts rename packages/anoncreds/src/services/registry/{RevocationListOptions.ts => RevocationStatusListOptions.ts} (70%) create mode 100644 packages/anoncreds/tests/anoncreds.test.ts diff --git a/packages/anoncreds/src/AnonCredsApi.ts b/packages/anoncreds/src/AnonCredsApi.ts new file mode 100644 index 0000000000..b52f4dbc0f --- /dev/null +++ b/packages/anoncreds/src/AnonCredsApi.ts @@ -0,0 +1,428 @@ +import type { AnonCredsCreateLinkSecretOptions } from './AnonCredsApiOptions' +import type { AnonCredsCredentialDefinition } from './models' +import type { + GetCredentialDefinitionReturn, + GetRevocationStatusListReturn, + GetRevocationRegistryDefinitionReturn, + GetSchemaReturn, + RegisterCredentialDefinitionReturn, + RegisterSchemaOptions, + RegisterSchemaReturn, +} from './services' +import type { Extensible } from './services/registry/base' + +import { AgentContext, inject, injectable } from '@aries-framework/core' + +import { AnonCredsModuleConfig } from './AnonCredsModuleConfig' +import { AnonCredsStoreRecordError } from './error' +import { + AnonCredsCredentialDefinitionPrivateRecord, + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsKeyCorrectnessProofRecord, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsLinkSecretRecord, + AnonCredsLinkSecretRepository, +} from './repository' +import { AnonCredsCredentialDefinitionRecord } from './repository/AnonCredsCredentialDefinitionRecord' +import { AnonCredsCredentialDefinitionRepository } from './repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRecord } from './repository/AnonCredsSchemaRecord' +import { AnonCredsSchemaRepository } from './repository/AnonCredsSchemaRepository' +import { AnonCredsCredentialDefinitionRecordMetadataKeys } from './repository/anonCredsCredentialDefinitionRecordMetadataTypes' +import { + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsIssuerService, + AnonCredsHolderService, +} from './services' +import { AnonCredsRegistryService } from './services/registry/AnonCredsRegistryService' + +@injectable() +export class AnonCredsApi { + public config: AnonCredsModuleConfig + + private agentContext: AgentContext + private anonCredsRegistryService: AnonCredsRegistryService + private anonCredsSchemaRepository: AnonCredsSchemaRepository + private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository + private anonCredsCredentialDefinitionPrivateRepository: AnonCredsCredentialDefinitionPrivateRepository + private anonCredsKeyCorrectnessProofRepository: AnonCredsKeyCorrectnessProofRepository + private anonCredsLinkSecretRepository: AnonCredsLinkSecretRepository + private anonCredsIssuerService: AnonCredsIssuerService + private anonCredsHolderService: AnonCredsHolderService + + public constructor( + agentContext: AgentContext, + anonCredsRegistryService: AnonCredsRegistryService, + config: AnonCredsModuleConfig, + @inject(AnonCredsIssuerServiceSymbol) anonCredsIssuerService: AnonCredsIssuerService, + @inject(AnonCredsHolderServiceSymbol) anonCredsHolderService: AnonCredsHolderService, + anonCredsSchemaRepository: AnonCredsSchemaRepository, + anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository, + anonCredsCredentialDefinitionPrivateRepository: AnonCredsCredentialDefinitionPrivateRepository, + anonCredsKeyCorrectnessProofRepository: AnonCredsKeyCorrectnessProofRepository, + anonCredsLinkSecretRepository: AnonCredsLinkSecretRepository + ) { + this.agentContext = agentContext + this.anonCredsRegistryService = anonCredsRegistryService + this.config = config + this.anonCredsIssuerService = anonCredsIssuerService + this.anonCredsHolderService = anonCredsHolderService + this.anonCredsSchemaRepository = anonCredsSchemaRepository + this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository + this.anonCredsCredentialDefinitionPrivateRepository = anonCredsCredentialDefinitionPrivateRepository + this.anonCredsKeyCorrectnessProofRepository = anonCredsKeyCorrectnessProofRepository + this.anonCredsLinkSecretRepository = anonCredsLinkSecretRepository + } + + /** + * Create a Link Secret, optionally indicating its ID and if it will be the default one + * If there is no default Link Secret, this will be set as default (even if setAsDefault is true). + * + */ + public async createLinkSecret(options?: AnonCredsCreateLinkSecretOptions) { + const { linkSecretId, linkSecretValue } = await this.anonCredsHolderService.createLinkSecret(this.agentContext, { + linkSecretId: options?.linkSecretId, + }) + + // In some cases we don't have the linkSecretValue. However we still want a record so we know which link secret ids are valid + const linkSecretRecord = new AnonCredsLinkSecretRecord({ linkSecretId, value: linkSecretValue }) + + // If it is the first link secret registered, set as default + const defaultLinkSecretRecord = await this.anonCredsLinkSecretRepository.findDefault(this.agentContext) + if (!defaultLinkSecretRecord || options?.setAsDefault) { + linkSecretRecord.setTag('isDefault', true) + } + + // Set the current default link secret as not default + if (defaultLinkSecretRecord && options?.setAsDefault) { + defaultLinkSecretRecord.setTag('isDefault', false) + await this.anonCredsLinkSecretRepository.update(this.agentContext, defaultLinkSecretRecord) + } + + await this.anonCredsLinkSecretRepository.save(this.agentContext, linkSecretRecord) + } + + /** + * Get a list of ids for the created link secrets + */ + public async getLinkSecretIds(): Promise { + const linkSecrets = await this.anonCredsLinkSecretRepository.getAll(this.agentContext) + + return linkSecrets.map((linkSecret) => linkSecret.linkSecretId) + } + + /** + * Retrieve a {@link AnonCredsSchema} from the registry associated + * with the {@link schemaId} + */ + public async getSchema(schemaId: string): Promise { + const failedReturnBase = { + resolutionMetadata: { + error: 'error', + message: `Unable to resolve schema ${schemaId}`, + }, + schemaId, + schemaMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(schemaId) + if (!registry) { + failedReturnBase.resolutionMetadata.error = 'unsupportedAnonCredsMethod' + failedReturnBase.resolutionMetadata.message = `Unable to resolve schema ${schemaId}: No registry found for identifier ${schemaId}` + return failedReturnBase + } + + try { + const result = await registry.getSchema(this.agentContext, schemaId) + return result + } catch (error) { + failedReturnBase.resolutionMetadata.message = `Unable to resolve schema ${schemaId}: ${error.message}` + return failedReturnBase + } + } + + public async registerSchema(options: RegisterSchemaOptions): Promise { + const failedReturnBase = { + schemaState: { + state: 'failed' as const, + schema: options.schema, + reason: `Error registering schema for issuerId ${options.schema.issuerId}`, + }, + registrationMetadata: {}, + schemaMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(options.schema.issuerId) + if (!registry) { + failedReturnBase.schemaState.reason = `Unable to register schema. No registry found for issuerId ${options.schema.issuerId}` + return failedReturnBase + } + + try { + const result = await registry.registerSchema(this.agentContext, options) + await this.storeSchemaRecord(result) + + return result + } catch (error) { + // Storage failed + if (error instanceof AnonCredsStoreRecordError) { + failedReturnBase.schemaState.reason = `Error storing schema record: ${error.message}` + return failedReturnBase + } + + // In theory registerSchema SHOULD NOT throw, but we can't know for sure + failedReturnBase.schemaState.reason = `Error registering schema: ${error.message}` + return failedReturnBase + } + } + + /** + * Retrieve a {@link AnonCredsCredentialDefinition} from the registry associated + * with the {@link credentialDefinitionId} + */ + public async getCredentialDefinition(credentialDefinitionId: string): Promise { + const failedReturnBase = { + resolutionMetadata: { + error: 'error', + message: `Unable to resolve credential definition ${credentialDefinitionId}`, + }, + credentialDefinitionId, + credentialDefinitionMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(credentialDefinitionId) + if (!registry) { + failedReturnBase.resolutionMetadata.error = 'unsupportedAnonCredsMethod' + failedReturnBase.resolutionMetadata.message = `Unable to resolve credential definition ${credentialDefinitionId}: No registry found for identifier ${credentialDefinitionId}` + return failedReturnBase + } + + try { + const result = await registry.getCredentialDefinition(this.agentContext, credentialDefinitionId) + return result + } catch (error) { + failedReturnBase.resolutionMetadata.message = `Unable to resolve credential definition ${credentialDefinitionId}: ${error.message}` + return failedReturnBase + } + } + + public async registerCredentialDefinition(options: { + credentialDefinition: Omit + // TODO: options should support supportsRevocation at some points + options: Extensible + }): Promise { + const failedReturnBase = { + credentialDefinitionState: { + state: 'failed' as const, + reason: `Error registering credential definition for issuerId ${options.credentialDefinition.issuerId}`, + }, + registrationMetadata: {}, + credentialDefinitionMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(options.credentialDefinition.issuerId) + if (!registry) { + failedReturnBase.credentialDefinitionState.reason = `Unable to register credential definition. No registry found for issuerId ${options.credentialDefinition.issuerId}` + return failedReturnBase + } + + const schemaRegistry = this.findRegistryForIdentifier(options.credentialDefinition.schemaId) + if (!schemaRegistry) { + failedReturnBase.credentialDefinitionState.reason = `Unable to register credential definition. No registry found for schemaId ${options.credentialDefinition.schemaId}` + return failedReturnBase + } + + try { + const schemaResult = await schemaRegistry.getSchema(this.agentContext, options.credentialDefinition.schemaId) + + if (!schemaResult.schema) { + failedReturnBase.credentialDefinitionState.reason = `error resolving schema with id ${options.credentialDefinition.schemaId}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + return failedReturnBase + } + + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = + await this.anonCredsIssuerService.createCredentialDefinition( + this.agentContext, + { + issuerId: options.credentialDefinition.issuerId, + schemaId: options.credentialDefinition.schemaId, + tag: options.credentialDefinition.tag, + supportRevocation: false, + schema: schemaResult.schema, + }, + // FIXME: Indy SDK requires the schema seq no to be passed in here. This is not ideal. + { + indyLedgerSchemaSeqNo: schemaResult.schemaMetadata.indyLedgerSeqNo, + } + ) + + const result = await registry.registerCredentialDefinition(this.agentContext, { + credentialDefinition, + options: options.options, + }) + + await this.storeCredentialDefinitionRecord(result, credentialDefinitionPrivate, keyCorrectnessProof) + + return result + } catch (error) { + // Storage failed + if (error instanceof AnonCredsStoreRecordError) { + failedReturnBase.credentialDefinitionState.reason = `Error storing credential definition records: ${error.message}` + return failedReturnBase + } + + // In theory registerCredentialDefinition SHOULD NOT throw, but we can't know for sure + failedReturnBase.credentialDefinitionState.reason = `Error registering credential definition: ${error.message}` + return failedReturnBase + } + } + + /** + * Retrieve a {@link AnonCredsRevocationRegistryDefinition} from the registry associated + * with the {@link revocationRegistryDefinitionId} + */ + public async getRevocationRegistryDefinition( + revocationRegistryDefinitionId: string + ): Promise { + const failedReturnBase = { + resolutionMetadata: { + error: 'error', + message: `Unable to resolve revocation registry ${revocationRegistryDefinitionId}`, + }, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(revocationRegistryDefinitionId) + if (!registry) { + failedReturnBase.resolutionMetadata.error = 'unsupportedAnonCredsMethod' + failedReturnBase.resolutionMetadata.message = `Unable to resolve revocation registry ${revocationRegistryDefinitionId}: No registry found for identifier ${revocationRegistryDefinitionId}` + return failedReturnBase + } + + try { + const result = await registry.getRevocationRegistryDefinition(this.agentContext, revocationRegistryDefinitionId) + return result + } catch (error) { + failedReturnBase.resolutionMetadata.message = `Unable to resolve revocation registry ${revocationRegistryDefinitionId}: ${error.message}` + return failedReturnBase + } + } + + /** + * Retrieve the {@link AnonCredsRevocationStatusList} for the given {@link timestamp} from the registry associated + * with the {@link revocationRegistryDefinitionId} + */ + public async getRevocationStatusList( + revocationRegistryDefinitionId: string, + timestamp: number + ): Promise { + const failedReturnBase = { + resolutionMetadata: { + error: 'error', + message: `Unable to resolve revocation status list for revocation registry ${revocationRegistryDefinitionId}`, + }, + revocationStatusListMetadata: {}, + } + + const registry = this.findRegistryForIdentifier(revocationRegistryDefinitionId) + if (!registry) { + failedReturnBase.resolutionMetadata.error = 'unsupportedAnonCredsMethod' + failedReturnBase.resolutionMetadata.message = `Unable to resolve revocation status list for revocation registry ${revocationRegistryDefinitionId}: No registry found for identifier ${revocationRegistryDefinitionId}` + return failedReturnBase + } + + try { + const result = await registry.getRevocationStatusList( + this.agentContext, + revocationRegistryDefinitionId, + timestamp + ) + return result + } catch (error) { + failedReturnBase.resolutionMetadata.message = `Unable to resolve revocation status list for revocation registry ${revocationRegistryDefinitionId}: ${error.message}` + return failedReturnBase + } + } + + private async storeCredentialDefinitionRecord( + result: RegisterCredentialDefinitionReturn, + credentialDefinitionPrivate?: Record, + keyCorrectnessProof?: Record + ): Promise { + try { + // If we have both the credentialDefinition and the credentialDefinitionId we will store a copy of the credential definition. We may need to handle an + // edge case in the future where we e.g. don't have the id yet, and it is registered through a different channel + if ( + result.credentialDefinitionState.credentialDefinition && + result.credentialDefinitionState.credentialDefinitionId + ) { + const credentialDefinitionRecord = new AnonCredsCredentialDefinitionRecord({ + credentialDefinitionId: result.credentialDefinitionState.credentialDefinitionId, + credentialDefinition: result.credentialDefinitionState.credentialDefinition, + }) + + // TODO: do we need to store this metadata? For indy, the registration metadata contains e.g. + // the indyLedgerSeqNo and the didIndyNamespace, but it can get quite big if complete transactions + // are stored in the metadata + credentialDefinitionRecord.metadata.set( + AnonCredsCredentialDefinitionRecordMetadataKeys.CredentialDefinitionMetadata, + result.credentialDefinitionMetadata + ) + credentialDefinitionRecord.metadata.set( + AnonCredsCredentialDefinitionRecordMetadataKeys.CredentialDefinitionRegistrationMetadata, + result.registrationMetadata + ) + + await this.anonCredsCredentialDefinitionRepository.save(this.agentContext, credentialDefinitionRecord) + + // Store Credential Definition private data (if provided by issuer service) + if (credentialDefinitionPrivate) { + const credentialDefinitionPrivateRecord = new AnonCredsCredentialDefinitionPrivateRecord({ + credentialDefinitionId: result.credentialDefinitionState.credentialDefinitionId, + value: credentialDefinitionPrivate, + }) + await this.anonCredsCredentialDefinitionPrivateRepository.save( + this.agentContext, + credentialDefinitionPrivateRecord + ) + } + + if (keyCorrectnessProof) { + const keyCorrectnessProofRecord = new AnonCredsKeyCorrectnessProofRecord({ + credentialDefinitionId: result.credentialDefinitionState.credentialDefinitionId, + value: keyCorrectnessProof, + }) + await this.anonCredsKeyCorrectnessProofRepository.save(this.agentContext, keyCorrectnessProofRecord) + } + } + } catch (error) { + throw new AnonCredsStoreRecordError(`Error storing credential definition records`, { cause: error }) + } + } + + private async storeSchemaRecord(result: RegisterSchemaReturn): Promise { + try { + // If we have both the schema and the schemaId we will store a copy of the schema. We may need to handle an + // edge case in the future where we e.g. don't have the id yet, and it is registered through a different channel + if (result.schemaState.schema && result.schemaState.schemaId) { + const schemaRecord = new AnonCredsSchemaRecord({ + schemaId: result.schemaState.schemaId, + schema: result.schemaState.schema, + }) + + await this.anonCredsSchemaRepository.save(this.agentContext, schemaRecord) + } + } catch (error) { + throw new AnonCredsStoreRecordError(`Error storing schema record`, { cause: error }) + } + } + + private findRegistryForIdentifier(identifier: string) { + try { + return this.anonCredsRegistryService.getRegistryForIdentifier(this.agentContext, identifier) + } catch { + return null + } + } +} diff --git a/packages/anoncreds/src/AnonCredsApiOptions.ts b/packages/anoncreds/src/AnonCredsApiOptions.ts new file mode 100644 index 0000000000..78a8e77728 --- /dev/null +++ b/packages/anoncreds/src/AnonCredsApiOptions.ts @@ -0,0 +1,4 @@ +export interface AnonCredsCreateLinkSecretOptions { + linkSecretId?: string + setAsDefault?: boolean +} diff --git a/packages/anoncreds/src/AnonCredsModule.ts b/packages/anoncreds/src/AnonCredsModule.ts index 0da6e242f7..3d6eff0b74 100644 --- a/packages/anoncreds/src/AnonCredsModule.ts +++ b/packages/anoncreds/src/AnonCredsModule.ts @@ -1,7 +1,15 @@ import type { AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' import type { DependencyManager, Module } from '@aries-framework/core' +import { AnonCredsApi } from './AnonCredsApi' import { AnonCredsModuleConfig } from './AnonCredsModuleConfig' +import { + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsLinkSecretRepository, +} from './repository' +import { AnonCredsCredentialDefinitionRepository } from './repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRepository } from './repository/AnonCredsSchemaRepository' import { AnonCredsRegistryService } from './services/registry/AnonCredsRegistryService' /** @@ -9,6 +17,7 @@ import { AnonCredsRegistryService } from './services/registry/AnonCredsRegistryS */ export class AnonCredsModule implements Module { public readonly config: AnonCredsModuleConfig + public api = AnonCredsApi public constructor(config: AnonCredsModuleConfigOptions) { this.config = new AnonCredsModuleConfig(config) @@ -19,5 +28,12 @@ export class AnonCredsModule implements Module { dependencyManager.registerInstance(AnonCredsModuleConfig, this.config) dependencyManager.registerSingleton(AnonCredsRegistryService) + + // Repositories + dependencyManager.registerSingleton(AnonCredsSchemaRepository) + dependencyManager.registerSingleton(AnonCredsCredentialDefinitionRepository) + dependencyManager.registerSingleton(AnonCredsCredentialDefinitionPrivateRepository) + dependencyManager.registerSingleton(AnonCredsKeyCorrectnessProofRepository) + dependencyManager.registerSingleton(AnonCredsLinkSecretRepository) } } diff --git a/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts b/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts index 90aa51ce66..f9c868c14c 100644 --- a/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts +++ b/packages/anoncreds/src/__tests__/AnonCredsModule.test.ts @@ -3,6 +3,13 @@ import type { DependencyManager } from '@aries-framework/core' import { AnonCredsModule } from '../AnonCredsModule' import { AnonCredsModuleConfig } from '../AnonCredsModuleConfig' +import { + AnonCredsSchemaRepository, + AnonCredsCredentialDefinitionRepository, + AnonCredsCredentialDefinitionPrivateRepository, + AnonCredsKeyCorrectnessProofRepository, + AnonCredsLinkSecretRepository, +} from '../repository' import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' const dependencyManager = { @@ -19,8 +26,13 @@ describe('AnonCredsModule', () => { }) anonCredsModule.register(dependencyManager) - expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(1) + expect(dependencyManager.registerSingleton).toHaveBeenCalledTimes(6) expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsRegistryService) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsSchemaRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsCredentialDefinitionRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsCredentialDefinitionPrivateRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsKeyCorrectnessProofRepository) + expect(dependencyManager.registerSingleton).toHaveBeenCalledWith(AnonCredsLinkSecretRepository) expect(dependencyManager.registerInstance).toHaveBeenCalledTimes(1) expect(dependencyManager.registerInstance).toHaveBeenCalledWith(AnonCredsModuleConfig, anonCredsModule.config) diff --git a/packages/anoncreds/src/error/AnonCredsStoreRecordError.ts b/packages/anoncreds/src/error/AnonCredsStoreRecordError.ts new file mode 100644 index 0000000000..11437d7b64 --- /dev/null +++ b/packages/anoncreds/src/error/AnonCredsStoreRecordError.ts @@ -0,0 +1,7 @@ +import { AnonCredsError } from './AnonCredsError' + +export class AnonCredsStoreRecordError extends AnonCredsError { + public constructor(message: string, { cause }: { cause?: Error } = {}) { + super(message, { cause }) + } +} diff --git a/packages/anoncreds/src/error/index.ts b/packages/anoncreds/src/error/index.ts index d9786950bf..6d25bc4dbb 100644 --- a/packages/anoncreds/src/error/index.ts +++ b/packages/anoncreds/src/error/index.ts @@ -1 +1,2 @@ export * from './AnonCredsError' +export * from './AnonCredsStoreRecordError' diff --git a/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts b/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts index 7e1e1909da..2449c81124 100644 --- a/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts +++ b/packages/anoncreds/src/formats/__tests__/LegacyIndyCredentialFormatService.test.ts @@ -77,7 +77,7 @@ describe('LegacyIndyCredentialFormatService', () => { options: {}, }) - const credentialDefinition = await anonCredsIssuerService.createCredentialDefinition( + const { credentialDefinition } = await anonCredsIssuerService.createCredentialDefinition( agentContext, { issuerId: indyDid, diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts index 759e343c2c..9ef264f501 100644 --- a/packages/anoncreds/src/index.ts +++ b/packages/anoncreds/src/index.ts @@ -1,5 +1,9 @@ export * from './models' export * from './services' export * from './error' +export * from './repository' export { AnonCredsModule } from './AnonCredsModule' export { AnonCredsModuleConfig, AnonCredsModuleConfigOptions } from './AnonCredsModuleConfig' +export { AnonCredsApi } from './AnonCredsApi' +export { LegacyIndyCredentialFormatService } from './formats/LegacyIndyCredentialFormatService' +export { AnonCredsRegistryService } from './services/registry/AnonCredsRegistryService' diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 40713b227d..b0e960afb8 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -1,4 +1,4 @@ -interface AnonCredsProofRequestRestriction { +export interface AnonCredsProofRequestRestriction { schema_id?: string schema_issuer_id?: string schema_name?: string diff --git a/packages/anoncreds/src/models/registry.ts b/packages/anoncreds/src/models/registry.ts index 1e5e6d7879..f4f3429ec2 100644 --- a/packages/anoncreds/src/models/registry.ts +++ b/packages/anoncreds/src/models/registry.ts @@ -32,7 +32,7 @@ export interface AnonCredsRevocationRegistryDefinition { tailsHash: string } -export interface AnonCredsRevocationList { +export interface AnonCredsRevocationStatusList { issuerId: string revRegId: string revocationList: number[] diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRecord.ts new file mode 100644 index 0000000000..bc0c1c99ee --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRecord.ts @@ -0,0 +1,41 @@ +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export interface AnonCredsCredentialDefinitionPrivateRecordProps { + id?: string + credentialDefinitionId: string + value: Record +} + +export type DefaultAnonCredsCredentialDefinitionPrivateTags = { + credentialDefinitionId: string +} + +export class AnonCredsCredentialDefinitionPrivateRecord extends BaseRecord< + DefaultAnonCredsCredentialDefinitionPrivateTags, + TagsBase +> { + public static readonly type = 'AnonCredsCredentialDefinitionPrivateRecord' + public readonly type = AnonCredsCredentialDefinitionPrivateRecord.type + + public readonly credentialDefinitionId!: string + public readonly value!: Record // TODO: Define structure + + public constructor(props: AnonCredsCredentialDefinitionPrivateRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.credentialDefinitionId = props.credentialDefinitionId + this.value = props.value + } + } + + public getTags() { + return { + ...this._tags, + credentialDefinitionId: this.credentialDefinitionId, + } + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRepository.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRepository.ts new file mode 100644 index 0000000000..31c7737143 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionPrivateRepository.ts @@ -0,0 +1,23 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +import { AnonCredsCredentialDefinitionPrivateRecord } from './AnonCredsCredentialDefinitionPrivateRecord' + +@injectable() +export class AnonCredsCredentialDefinitionPrivateRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsCredentialDefinitionPrivateRecord, 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/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts new file mode 100644 index 0000000000..f9c7df43f7 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts @@ -0,0 +1,50 @@ +import type { AnonCredsCredentialDefinitionRecordMetadata } from './anonCredsCredentialDefinitionRecordMetadataTypes' +import type { AnonCredsCredentialDefinition } from '../models' +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export interface AnonCredsCredentialDefinitionRecordProps { + id?: string + credentialDefinitionId: string + credentialDefinition: AnonCredsCredentialDefinition +} + +export type DefaultAnonCredsCredentialDefinitionTags = { + schemaId: string + credentialDefinitionId: string + issuerId: string + tag: string +} + +export class AnonCredsCredentialDefinitionRecord extends BaseRecord< + DefaultAnonCredsCredentialDefinitionTags, + TagsBase, + AnonCredsCredentialDefinitionRecordMetadata +> { + public static readonly type = 'AnonCredsCredentialDefinitionRecord' + public readonly type = AnonCredsCredentialDefinitionRecord.type + + public readonly credentialDefinitionId!: string + public readonly credentialDefinition!: AnonCredsCredentialDefinition + + public constructor(props: AnonCredsCredentialDefinitionRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.credentialDefinitionId = props.credentialDefinitionId + this.credentialDefinition = props.credentialDefinition + } + } + + public getTags() { + return { + ...this._tags, + credentialDefinitionId: this.credentialDefinitionId, + schemaId: this.credentialDefinition.schemaId, + issuerId: this.credentialDefinition.issuerId, + tag: this.credentialDefinition.tag, + } + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts new file mode 100644 index 0000000000..7677dd76b8 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts @@ -0,0 +1,23 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +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/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRecord.ts b/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRecord.ts new file mode 100644 index 0000000000..cac331bd6c --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRecord.ts @@ -0,0 +1,41 @@ +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export interface AnonCredsKeyCorrectnessProofRecordProps { + id?: string + credentialDefinitionId: string + value: Record +} + +export type DefaultAnonCredsKeyCorrectnessProofPrivateTags = { + credentialDefinitionId: string +} + +export class AnonCredsKeyCorrectnessProofRecord extends BaseRecord< + DefaultAnonCredsKeyCorrectnessProofPrivateTags, + TagsBase +> { + public static readonly type = 'AnonCredsKeyCorrectnessProofRecord' + public readonly type = AnonCredsKeyCorrectnessProofRecord.type + + public readonly credentialDefinitionId!: string + public readonly value!: Record // TODO: Define structure + + public constructor(props: AnonCredsKeyCorrectnessProofRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.credentialDefinitionId = props.credentialDefinitionId + this.value = props.value + } + } + + public getTags() { + return { + ...this._tags, + credentialDefinitionId: this.credentialDefinitionId, + } + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRepository.ts b/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRepository.ts new file mode 100644 index 0000000000..959ba8b4a5 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsKeyCorrectnessProofRepository.ts @@ -0,0 +1,23 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +import { AnonCredsKeyCorrectnessProofRecord } from './AnonCredsKeyCorrectnessProofRecord' + +@injectable() +export class AnonCredsKeyCorrectnessProofRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsKeyCorrectnessProofRecord, 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/anoncreds/src/repository/AnonCredsLinkSecretRecord.ts b/packages/anoncreds/src/repository/AnonCredsLinkSecretRecord.ts new file mode 100644 index 0000000000..ffb775526e --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsLinkSecretRecord.ts @@ -0,0 +1,42 @@ +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export interface AnonCredsLinkSecretRecordProps { + id?: string + linkSecretId: string + value?: string // If value is not provided, only reference to link secret is stored in regular storage +} + +export type DefaultAnonCredsLinkSecretTags = { + linkSecretId: string +} + +export type CustomAnonCredsLinkSecretTags = TagsBase & { + isDefault?: boolean +} + +export class AnonCredsLinkSecretRecord extends BaseRecord { + public static readonly type = 'AnonCredsLinkSecretRecord' + public readonly type = AnonCredsLinkSecretRecord.type + + public readonly linkSecretId!: string + public readonly value?: string + + public constructor(props: AnonCredsLinkSecretRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.linkSecretId = props.linkSecretId + this.value = props.value + } + } + + public getTags() { + return { + ...this._tags, + linkSecretId: this.linkSecretId, + } + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsLinkSecretRepository.ts b/packages/anoncreds/src/repository/AnonCredsLinkSecretRepository.ts new file mode 100644 index 0000000000..a4b69b08db --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsLinkSecretRepository.ts @@ -0,0 +1,31 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +import { AnonCredsLinkSecretRecord } from './AnonCredsLinkSecretRecord' + +@injectable() +export class AnonCredsLinkSecretRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsLinkSecretRecord, storageService, eventEmitter) + } + + public async getDefault(agentContext: AgentContext) { + return this.getSingleByQuery(agentContext, { isDefault: true }) + } + + public async findDefault(agentContext: AgentContext) { + return this.findSingleByQuery(agentContext, { isDefault: true }) + } + + public async getByLinkSecretId(agentContext: AgentContext, linkSecretId: string) { + return this.getSingleByQuery(agentContext, { linkSecretId }) + } + + public async findByLinkSecretId(agentContext: AgentContext, linkSecretId: string) { + return this.findSingleByQuery(agentContext, { linkSecretId }) + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsSchemaRecord.ts b/packages/anoncreds/src/repository/AnonCredsSchemaRecord.ts new file mode 100644 index 0000000000..13ad5d757c --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsSchemaRecord.ts @@ -0,0 +1,50 @@ +import type { AnonCredsSchemaRecordMetadata } from './anonCredsSchemaRecordMetadataTypes' +import type { AnonCredsSchema } from '../models' +import type { TagsBase } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' + +export interface AnonCredsSchemaRecordProps { + id?: string + schemaId: string + schema: AnonCredsSchema +} + +export type DefaultAnonCredsSchemaTags = { + schemaId: string + issuerId: string + schemaName: string + schemaVersion: string +} + +export class AnonCredsSchemaRecord extends BaseRecord< + DefaultAnonCredsSchemaTags, + TagsBase, + AnonCredsSchemaRecordMetadata +> { + public static readonly type = 'AnonCredsSchemaRecord' + public readonly type = AnonCredsSchemaRecord.type + + public readonly schemaId!: string + public readonly schema!: AnonCredsSchema + + public constructor(props: AnonCredsSchemaRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.schema = props.schema + this.schemaId = props.schemaId + } + } + + public getTags() { + return { + ...this._tags, + schemaId: this.schemaId, + issuerId: this.schema.issuerId, + schemaName: this.schema.name, + schemaVersion: this.schema.version, + } + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsSchemaRepository.ts b/packages/anoncreds/src/repository/AnonCredsSchemaRepository.ts new file mode 100644 index 0000000000..0d0ab84b9f --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsSchemaRepository.ts @@ -0,0 +1,23 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, inject, injectable } from '@aries-framework/core' + +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/anoncreds/src/repository/anonCredsCredentialDefinitionRecordMetadataTypes.ts b/packages/anoncreds/src/repository/anonCredsCredentialDefinitionRecordMetadataTypes.ts new file mode 100644 index 0000000000..05806802e4 --- /dev/null +++ b/packages/anoncreds/src/repository/anonCredsCredentialDefinitionRecordMetadataTypes.ts @@ -0,0 +1,11 @@ +import type { Extensible } from '../services/registry/base' + +export enum AnonCredsCredentialDefinitionRecordMetadataKeys { + CredentialDefinitionRegistrationMetadata = '_internal/anonCredsCredentialDefinitionRegistrationMetadata', + CredentialDefinitionMetadata = '_internal/anonCredsCredentialDefinitionMetadata', +} + +export type AnonCredsCredentialDefinitionRecordMetadata = { + [AnonCredsCredentialDefinitionRecordMetadataKeys.CredentialDefinitionRegistrationMetadata]: Extensible + [AnonCredsCredentialDefinitionRecordMetadataKeys.CredentialDefinitionMetadata]: Extensible +} diff --git a/packages/anoncreds/src/repository/anonCredsSchemaRecordMetadataTypes.ts b/packages/anoncreds/src/repository/anonCredsSchemaRecordMetadataTypes.ts new file mode 100644 index 0000000000..9880a50625 --- /dev/null +++ b/packages/anoncreds/src/repository/anonCredsSchemaRecordMetadataTypes.ts @@ -0,0 +1,11 @@ +import type { Extensible } from '../services/registry/base' + +export enum AnonCredsSchemaRecordMetadataKeys { + SchemaRegistrationMetadata = '_internal/anonCredsSchemaRegistrationMetadata', + SchemaMetadata = '_internal/anonCredsSchemaMetadata', +} + +export type AnonCredsSchemaRecordMetadata = { + [AnonCredsSchemaRecordMetadataKeys.SchemaRegistrationMetadata]: Extensible + [AnonCredsSchemaRecordMetadataKeys.SchemaMetadata]: Extensible +} diff --git a/packages/anoncreds/src/repository/index.ts b/packages/anoncreds/src/repository/index.ts new file mode 100644 index 0000000000..5e17e19941 --- /dev/null +++ b/packages/anoncreds/src/repository/index.ts @@ -0,0 +1,10 @@ +export * from './AnonCredsCredentialDefinitionRecord' +export * from './AnonCredsCredentialDefinitionRepository' +export * from './AnonCredsCredentialDefinitionPrivateRecord' +export * from './AnonCredsCredentialDefinitionPrivateRepository' +export * from './AnonCredsKeyCorrectnessProofRecord' +export * from './AnonCredsKeyCorrectnessProofRepository' +export * from './AnonCredsLinkSecretRecord' +export * from './AnonCredsLinkSecretRepository' +export * from './AnonCredsSchemaRecord' +export * from './AnonCredsSchemaRepository' diff --git a/packages/anoncreds/src/services/AnonCredsHolderService.ts b/packages/anoncreds/src/services/AnonCredsHolderService.ts index a7c0dcb22e..85e51ce529 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderService.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderService.ts @@ -6,6 +6,8 @@ import type { StoreCredentialOptions, GetCredentialsForProofRequestOptions, GetCredentialsForProofRequestReturn, + CreateLinkSecretReturn, + CreateLinkSecretOptions, } from './AnonCredsHolderServiceOptions' import type { AnonCredsCredentialInfo } from '../models' import type { AnonCredsProof } from '../models/exchange' @@ -14,6 +16,8 @@ import type { AgentContext } from '@aries-framework/core' export const AnonCredsHolderServiceSymbol = Symbol('AnonCredsHolderService') export interface AnonCredsHolderService { + createLinkSecret(agentContext: AgentContext, options: CreateLinkSecretOptions): Promise + createProof(agentContext: AgentContext, options: CreateProofOptions): Promise storeCredential( agentContext: AgentContext, diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index 728482ff33..fcbc5e913c 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -12,7 +12,7 @@ import type { } from '../models/exchange' import type { AnonCredsCredentialDefinition, - AnonCredsRevocationList, + AnonCredsRevocationStatusList, AnonCredsRevocationRegistryDefinition, AnonCredsSchema, } from '../models/registry' @@ -36,8 +36,8 @@ export interface CreateProofOptions { // tails file MUST already be downloaded on a higher level and stored tailsFilePath: string definition: AnonCredsRevocationRegistryDefinition - revocationLists: { - [timestamp: string]: AnonCredsRevocationList + revocationStatusLists: { + [timestamp: string]: AnonCredsRevocationStatusList } } } @@ -81,9 +81,19 @@ export type GetCredentialsForProofRequestReturn = Array<{ export interface CreateCredentialRequestOptions { credentialOffer: AnonCredsCredentialOffer credentialDefinition: AnonCredsCredentialDefinition + linkSecretId?: string } export interface CreateCredentialRequestReturn { credentialRequest: AnonCredsCredentialRequest credentialRequestMetadata: AnonCredsCredentialRequestMetadata } + +export interface CreateLinkSecretOptions { + linkSecretId?: string +} + +export interface CreateLinkSecretReturn { + linkSecretId: string + linkSecretValue?: string +} diff --git a/packages/anoncreds/src/services/AnonCredsIssuerService.ts b/packages/anoncreds/src/services/AnonCredsIssuerService.ts index 41cb4ebf9f..3090b1759b 100644 --- a/packages/anoncreds/src/services/AnonCredsIssuerService.ts +++ b/packages/anoncreds/src/services/AnonCredsIssuerService.ts @@ -4,9 +4,10 @@ import type { CreateCredentialOfferOptions, CreateCredentialReturn, CreateCredentialOptions, + CreateCredentialDefinitionReturn, } from './AnonCredsIssuerServiceOptions' import type { AnonCredsCredentialOffer } from '../models/exchange' -import type { AnonCredsCredentialDefinition, AnonCredsSchema } from '../models/registry' +import type { AnonCredsSchema } from '../models/registry' import type { AgentContext } from '@aries-framework/core' export const AnonCredsIssuerServiceSymbol = Symbol('AnonCredsIssuerService') @@ -20,7 +21,7 @@ export interface AnonCredsIssuerService { agentContext: AgentContext, options: CreateCredentialDefinitionOptions, metadata?: Record - ): Promise + ): Promise createCredentialOffer( agentContext: AgentContext, diff --git a/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts index 58d6cd9048..c7da246b9b 100644 --- a/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts @@ -4,7 +4,7 @@ import type { AnonCredsCredentialRequest, AnonCredsCredentialValues, } from '../models/exchange' -import type { AnonCredsSchema } from '../models/registry' +import type { AnonCredsCredentialDefinition, AnonCredsSchema } from '../models/registry' export interface CreateSchemaOptions { issuerId: string @@ -39,3 +39,9 @@ export interface CreateCredentialReturn { credential: AnonCredsCredential credentialRevocationId?: string } + +export interface CreateCredentialDefinitionReturn { + credentialDefinition: AnonCredsCredentialDefinition + credentialDefinitionPrivate?: Record + keyCorrectnessProof?: Record +} diff --git a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts index f3ecb3b70c..85593764af 100644 --- a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts @@ -1,7 +1,7 @@ import type { AnonCredsProof, AnonCredsProofRequest } from '../models/exchange' import type { AnonCredsCredentialDefinition, - AnonCredsRevocationList, + AnonCredsRevocationStatusList, AnonCredsRevocationRegistryDefinition, AnonCredsSchema, } from '../models/registry' @@ -23,8 +23,8 @@ export interface VerifyProofOptions { // as a verifier. This is just following the data models from the AnonCreds spec, but for e.g. indy // this means we need to retrieve _ALL_ deltas from the ledger to verify a proof. While currently we // only need to fetch the registry. - revocationLists: { - [timestamp: number]: AnonCredsRevocationList + revocationStatusLists: { + [timestamp: number]: AnonCredsRevocationStatusList } } } diff --git a/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts b/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts index e3061043dd..870eb90571 100644 --- a/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts +++ b/packages/anoncreds/src/services/registry/AnonCredsRegistry.ts @@ -3,8 +3,8 @@ import type { RegisterCredentialDefinitionOptions, RegisterCredentialDefinitionReturn, } from './CredentialDefinitionOptions' -import type { GetRevocationListReturn } from './RevocationListOptions' import type { GetRevocationRegistryDefinitionReturn } from './RevocationRegistryDefinitionOptions' +import type { GetRevocationStatusListReturn } from './RevocationStatusListOptions' import type { GetSchemaReturn, RegisterSchemaOptions, RegisterSchemaReturn } from './SchemaOptions' import type { AgentContext } from '@aries-framework/core' @@ -37,12 +37,11 @@ export interface AnonCredsRegistry { // options: RegisterRevocationRegistryDefinitionOptions // ): Promise - // TODO: The name of this data model is still tbd. - getRevocationList( + getRevocationStatusList( agentContext: AgentContext, revocationRegistryId: string, timestamp: number - ): Promise + ): Promise // TODO: issuance of revocable credentials // registerRevocationList( diff --git a/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts b/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts index a860d1e8f5..23c393bb38 100644 --- a/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts +++ b/packages/anoncreds/src/services/registry/AnonCredsRegistryService.ts @@ -20,7 +20,7 @@ export class AnonCredsRegistryService { const registry = registries.find((registry) => registry.supportedIdentifier.test(identifier)) if (!registry) { - throw new AnonCredsError(`No AnonCredsRegistry registered for identifier '${registry}'`) + throw new AnonCredsError(`No AnonCredsRegistry registered for identifier '${identifier}'`) } return registry diff --git a/packages/anoncreds/src/services/registry/RevocationListOptions.ts b/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts similarity index 70% rename from packages/anoncreds/src/services/registry/RevocationListOptions.ts rename to packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts index f3a07dc686..6396fe6df0 100644 --- a/packages/anoncreds/src/services/registry/RevocationListOptions.ts +++ b/packages/anoncreds/src/services/registry/RevocationStatusListOptions.ts @@ -1,10 +1,10 @@ import type { AnonCredsResolutionMetadata, Extensible } from './base' -import type { AnonCredsRevocationList } from '../../models/registry' +import type { AnonCredsRevocationStatusList } from '../../models/registry' -export interface GetRevocationListReturn { - revocationList?: AnonCredsRevocationList +export interface GetRevocationStatusListReturn { + revocationStatusList?: AnonCredsRevocationStatusList resolutionMetadata: AnonCredsResolutionMetadata - revocationListMetadata: Extensible + revocationStatusListMetadata: Extensible } // TODO: Support for issuance of revocable credentials diff --git a/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts b/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts index 553b9e626c..2cb39bc2e5 100644 --- a/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts +++ b/packages/anoncreds/src/services/registry/__tests__/AnonCredsRegistryService.test.ts @@ -32,7 +32,7 @@ describe('AnonCredsRegistryService', () => { expect(anonCredsRegistryService.getRegistryForIdentifier(agentContext, 'b')).toEqual(registryTwo) }) - test('throws AnonCredsError if no registry is found for the given identifier', () => { + test('throws AnonCredsError if no registry is found for the given identifier', async () => { expect(() => anonCredsRegistryService.getRegistryForIdentifier(agentContext, 'c')).toThrow(AnonCredsError) }) }) diff --git a/packages/anoncreds/src/services/registry/index.ts b/packages/anoncreds/src/services/registry/index.ts index 5d36ce3dd9..fd154074fd 100644 --- a/packages/anoncreds/src/services/registry/index.ts +++ b/packages/anoncreds/src/services/registry/index.ts @@ -2,5 +2,5 @@ export * from './AnonCredsRegistry' export * from './CredentialDefinitionOptions' export * from './SchemaOptions' export * from './RevocationRegistryDefinitionOptions' -export * from './RevocationListOptions' +export * from './RevocationStatusListOptions' export { AnonCredsResolutionMetadata } from './base' diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts index a1426fad46..18bd9cfaab 100644 --- a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -8,7 +8,9 @@ import type { RegisterCredentialDefinitionOptions, RegisterCredentialDefinitionReturn, GetRevocationRegistryDefinitionReturn, - GetRevocationListReturn, + GetRevocationStatusListReturn, + AnonCredsRevocationStatusList, + AnonCredsRevocationRegistryDefinition, AnonCredsSchema, AnonCredsCredentialDefinition, } from '../src' @@ -26,8 +28,27 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { // we want, but the indy-sdk is picky about the identifier format. public readonly supportedIdentifier = /^[a-zA-Z0-9]{21,22}/ - private schemas: Record = {} - private credentialDefinitions: Record = {} + private schemas: Record + private credentialDefinitions: Record + private revocationRegistryDefinitions: Record + private revocationStatusLists: Record> + + public constructor({ + existingSchemas = {}, + existingCredentialDefinitions = {}, + existingRevocationRegistryDefinitions = {}, + existingRevocationStatusLists = {}, + }: { + existingSchemas?: Record + existingCredentialDefinitions?: Record + existingRevocationRegistryDefinitions?: Record + existingRevocationStatusLists?: Record> + } = {}) { + this.schemas = existingSchemas + this.credentialDefinitions = existingCredentialDefinitions + this.revocationRegistryDefinitions = existingRevocationRegistryDefinitions + this.revocationStatusLists = existingRevocationStatusLists + } public async getSchema(agentContext: AgentContext, schemaId: string): Promise { const schema = this.schemas[schemaId] @@ -40,11 +61,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { message: `Schema with id ${schemaId} not found in memory registry`, }, schemaId, - schemaMetadata: { - // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. - // For this reason we return it in the metadata. - indyLedgerSeqNo, - }, + schemaMetadata: {}, } } @@ -52,7 +69,11 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { resolutionMetadata: {}, schema, schemaId, - schemaMetadata: {}, + schemaMetadata: { + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo, + }, } } @@ -125,19 +146,53 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { } } - public getRevocationRegistryDefinition( + public async getRevocationRegistryDefinition( agentContext: AgentContext, revocationRegistryDefinitionId: string ): Promise { - throw new Error('Method not implemented.') + const revocationRegistryDefinition = this.revocationRegistryDefinitions[revocationRegistryDefinitionId] + + if (!revocationRegistryDefinition) { + return { + resolutionMetadata: { + error: 'notFound', + message: `Revocation registry definition with id ${revocationRegistryDefinition} not found in memory registry`, + }, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } + } + + return { + resolutionMetadata: {}, + revocationRegistryDefinition, + revocationRegistryDefinitionId, + revocationRegistryDefinitionMetadata: {}, + } } - public getRevocationList( + public async getRevocationStatusList( agentContext: AgentContext, revocationRegistryId: string, timestamp: number - ): Promise { - throw new Error('Method not implemented.') + ): Promise { + const revocationStatusLists = this.revocationStatusLists[revocationRegistryId] + + if (!revocationStatusLists || !revocationStatusLists[timestamp]) { + return { + resolutionMetadata: { + error: 'notFound', + message: `Revocation status list for revocation registry with id ${revocationRegistryId} not found in memory registry`, + }, + revocationStatusListMetadata: {}, + } + } + + return { + resolutionMetadata: {}, + revocationStatusList: revocationStatusLists[timestamp], + revocationStatusListMetadata: {}, + } } } diff --git a/packages/anoncreds/tests/anoncreds.test.ts b/packages/anoncreds/tests/anoncreds.test.ts new file mode 100644 index 0000000000..e7abd466c4 --- /dev/null +++ b/packages/anoncreds/tests/anoncreds.test.ts @@ -0,0 +1,312 @@ +import { Agent, KeyDerivationMethod } from '@aries-framework/core' +import { agentDependencies } from '@aries-framework/node' + +import { IndySdkModule } from '../../indy-sdk/src/IndySdkModule' +import { AnonCredsCredentialDefinitionRepository, AnonCredsModule, AnonCredsSchemaRepository } from '../src' + +import { InMemoryAnonCredsRegistry } from './InMemoryAnonCredsRegistry' + +const existingSchemas = { + '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0': { + attrNames: ['one', 'two'], + issuerId: '7Cd2Yj9yEZNcmNoH54tq9i', + name: 'Test Schema', + version: '1.0.0', + }, +} + +const existingCredentialDefinitions = { + 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG': { + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + tag: 'TAG', + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + type: 'CL', + value: { + primary: { + n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', + s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', + r: { + one: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', + master_secret: + '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', + two: '60366631925664005237432731340682977203246802182440530784833565276111958129922833461368205267143124766208499918438803966972947830682551774196763124331578934778868938718942789067536194229546670608604626738087066151521062180022991840618459591148096543440942293686250499935227881144460486543061212259250663566176469333982946568767707989969471450673037590849807300874360022327312564559087769485266016496010132793446151658150957771177955095876947792797176338483943233433284791481746843006255371654617950568875773118157773566188096075078351362095061968279597354733768049622048871890495958175847017320945873812850638157518451', + }, + rctxt: + '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', + z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', + }, + }, + }, +} as const + +const existingRevocationRegistryDefinitions = { + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG': { + credDefId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + maxCredNum: 100, + type: 'CL_ACCUM', + publicKeys: { + accumKey: { + z: 'ab81257c-be63-4051-9e21-c7d384412f64', + }, + }, + tag: 'TAG', + tailsHash: 'ab81257c-be63-4051-9e21-c7d384412f64', + tailsLocation: 'http://localhost:7200/tails', + }, +} as const + +const existingRevocationStatusLists = { + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG': { + 10123: { + currentAccumulator: 'ab81257c-be63-4051-9e21-c7d384412f64', + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + revocationList: [1, 0, 1], + revRegId: 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG', + timestamp: 10123, + }, + }, +} + +const agent = new Agent({ + config: { + label: '@aries-framework/anoncreds', + walletConfig: { + id: '@aries-framework/anoncreds', + key: 'CwNJroKHTSSj3XvE7ZAnuKiTn2C4QkFvxEqfm5rzhNrb', + keyDerivationMethod: KeyDerivationMethod.Raw, + }, + }, + modules: { + indySdk: new IndySdkModule({ + indySdk: agentDependencies.indy, + }), + anoncreds: new AnonCredsModule({ + registries: [ + new InMemoryAnonCredsRegistry({ + existingSchemas, + existingCredentialDefinitions, + existingRevocationRegistryDefinitions, + existingRevocationStatusLists, + }), + ], + }), + }, + dependencies: agentDependencies, +}) + +describe('AnonCreds API', () => { + beforeEach(async () => { + await agent.initialize() + }) + + afterEach(async () => { + await agent.wallet.delete() + await agent.shutdown() + }) + + test('create and get link secret', async () => { + await agent.modules.anoncreds.createLinkSecret({ + linkSecretId: 'anoncreds-link-secret', + }) + + const linkSecretIds = await agent.modules.anoncreds.getLinkSecretIds() + + expect(linkSecretIds).toEqual(['anoncreds-link-secret']) + }) + + test('register a schema', async () => { + const schemaResult = await agent.modules.anoncreds.registerSchema({ + options: {}, + schema: { + attrNames: ['name', 'age'], + issuerId: '6xDN7v3AiGgusRp4bqZACZ', + name: 'Employee Credential', + version: '1.0.0', + }, + }) + + expect(schemaResult).toEqual({ + registrationMetadata: {}, + schemaMetadata: { indyLedgerSeqNo: 16908 }, + schemaState: { + state: 'finished', + schema: { + attrNames: ['name', 'age'], + issuerId: '6xDN7v3AiGgusRp4bqZACZ', + name: 'Employee Credential', + version: '1.0.0', + }, + schemaId: '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0', + }, + }) + + // Check if record was created + const anonCredsSchemaRepository = agent.dependencyManager.resolve(AnonCredsSchemaRepository) + const schemaRecord = await anonCredsSchemaRepository.getBySchemaId( + agent.context, + '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0' + ) + + expect(schemaRecord).toMatchObject({ + schemaId: '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0', + schema: { + attrNames: ['name', 'age'], + issuerId: '6xDN7v3AiGgusRp4bqZACZ', + name: 'Employee Credential', + version: '1.0.0', + }, + }) + + expect(schemaRecord.getTags()).toEqual({ + schemaId: '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0', + issuerId: '6xDN7v3AiGgusRp4bqZACZ', + schemaName: 'Employee Credential', + schemaVersion: '1.0.0', + }) + }) + + test('resolve a schema', async () => { + const schemaResult = await agent.modules.anoncreds.getSchema('7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0') + + expect(schemaResult).toEqual({ + resolutionMetadata: {}, + schemaMetadata: { indyLedgerSeqNo: 75206 }, + schema: { + attrNames: ['one', 'two'], + issuerId: '7Cd2Yj9yEZNcmNoH54tq9i', + name: 'Test Schema', + version: '1.0.0', + }, + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + }) + }) + + test('register a credential definition', async () => { + // NOTE: the indy-sdk MUST have a did created, we can't just create a key + await agent.context.wallet.initPublicDid({ seed: '00000000000000000000000000000My1' }) + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const issuerId = agent.context.wallet.publicDid!.did + + const credentialDefinitionResult = await agent.modules.anoncreds.registerCredentialDefinition({ + credentialDefinition: { + issuerId, + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + tag: 'TAG', + }, + options: {}, + }) + + expect(credentialDefinitionResult).toEqual({ + registrationMetadata: {}, + credentialDefinitionMetadata: {}, + credentialDefinitionState: { + state: 'finished', + credentialDefinition: { + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + tag: 'TAG', + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + type: 'CL', + value: { + primary: { + n: expect.any(String), + s: expect.any(String), + r: { + one: expect.any(String), + master_secret: expect.any(String), + two: expect.any(String), + }, + rctxt: expect.any(String), + z: expect.any(String), + }, + }, + }, + credentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + }, + }) + + // Check if record was created + const anonCredsCredentialDefinitionRepository = agent.dependencyManager.resolve( + AnonCredsCredentialDefinitionRepository + ) + const credentialDefinitionRecord = await anonCredsCredentialDefinitionRepository.getByCredentialDefinitionId( + agent.context, + 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG' + ) + + expect(credentialDefinitionRecord).toMatchObject({ + credentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + credentialDefinition: { + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + tag: 'TAG', + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + type: 'CL', + value: { + primary: { + n: expect.any(String), + s: expect.any(String), + r: { + one: expect.any(String), + master_secret: expect.any(String), + two: expect.any(String), + }, + rctxt: expect.any(String), + z: expect.any(String), + }, + }, + }, + }) + + expect(credentialDefinitionRecord.getTags()).toEqual({ + credentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', + issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + tag: 'TAG', + }) + }) + + test('resolve a credential definition', async () => { + const credentialDefinitionResult = await agent.modules.anoncreds.getCredentialDefinition( + 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG' + ) + + expect(credentialDefinitionResult).toEqual({ + resolutionMetadata: {}, + credentialDefinitionMetadata: {}, + credentialDefinition: existingCredentialDefinitions['VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG'], + credentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + }) + }) + + test('resolve a revocation regsitry definition', async () => { + const revocationRegistryDefinition = await agent.modules.anoncreds.getRevocationRegistryDefinition( + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG' + ) + + expect(revocationRegistryDefinition).toEqual({ + revocationRegistryDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG', + revocationRegistryDefinition: + existingRevocationRegistryDefinitions[ + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG' + ], + resolutionMetadata: {}, + revocationRegistryDefinitionMetadata: {}, + }) + }) + + test('resolve a revocation status list', async () => { + const revocationStatusList = await agent.modules.anoncreds.getRevocationStatusList( + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG', + 10123 + ) + + expect(revocationStatusList).toEqual({ + revocationStatusList: + existingRevocationStatusLists[ + 'VsKV7grR1BUE29mG2Fm2kX:4:VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG:CL_ACCUM:TAG' + ][10123], + resolutionMetadata: {}, + revocationStatusListMetadata: {}, + }) + }) +}) diff --git a/packages/anoncreds/tests/setup.ts b/packages/anoncreds/tests/setup.ts index 719a473b6e..b60b932be5 100644 --- a/packages/anoncreds/tests/setup.ts +++ b/packages/anoncreds/tests/setup.ts @@ -1 +1 @@ -jest.setTimeout(10000) +jest.setTimeout(25000) diff --git a/packages/indy-sdk/src/IndySdkModule.ts b/packages/indy-sdk/src/IndySdkModule.ts index ea3baa5a9a..20574f3d46 100644 --- a/packages/indy-sdk/src/IndySdkModule.ts +++ b/packages/indy-sdk/src/IndySdkModule.ts @@ -1,8 +1,18 @@ import type { IndySdkModuleConfigOptions } from './IndySdkModuleConfig' import type { DependencyManager, Module } from '@aries-framework/core' +import { + AnonCredsHolderServiceSymbol, + AnonCredsIssuerServiceSymbol, + AnonCredsVerifierServiceSymbol, +} from '@aries-framework/anoncreds' +import { InjectionSymbols } from '@aries-framework/core' + import { IndySdkModuleConfig } from './IndySdkModuleConfig' +import { IndySdkHolderService, IndySdkIssuerService, IndySdkVerifierService } from './anoncreds' +import { IndySdkStorageService } from './storage' import { IndySdkSymbol } from './types' +import { IndySdkWallet } from './wallet' export class IndySdkModule implements Module { public readonly config: IndySdkModuleConfig @@ -13,5 +23,13 @@ export class IndySdkModule implements Module { public register(dependencyManager: DependencyManager) { dependencyManager.registerInstance(IndySdkSymbol, this.config.indySdk) + + // NOTE: for now we are registering the needed indy services. We may want to make this + // more explicit and require the user to register the services they need on the specific modules. + dependencyManager.registerSingleton(InjectionSymbols.Wallet, IndySdkWallet) + dependencyManager.registerSingleton(InjectionSymbols.StorageService, IndySdkStorageService) + dependencyManager.registerSingleton(AnonCredsIssuerServiceSymbol, IndySdkIssuerService) + dependencyManager.registerSingleton(AnonCredsHolderServiceSymbol, IndySdkHolderService) + dependencyManager.registerSingleton(AnonCredsVerifierServiceSymbol, IndySdkVerifierService) } } diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index ffe975b7e1..7ddb4a5db5 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -2,7 +2,7 @@ import type { IndySdk } from '../../types' import type { AnonCredsRegistry, GetCredentialDefinitionReturn, - GetRevocationListReturn, + GetRevocationStatusListReturn, GetRevocationRegistryDefinitionReturn, GetSchemaReturn, RegisterCredentialDefinitionOptions, @@ -25,7 +25,7 @@ import { indySdkAnonCredsRegistryIdentifierRegex, } from '../utils/identifiers' import { - anonCredsRevocationListFromIndySdk, + anonCredsRevocationStatusListFromIndySdk, anonCredsRevocationRegistryDefinitionFromIndySdk, } from '../utils/transform' @@ -417,11 +417,11 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } } - public async getRevocationList( + public async getRevocationStatusList( agentContext: AgentContext, revocationRegistryId: string, timestamp: number - ): Promise { + ): Promise { try { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) @@ -470,7 +470,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { resolutionMetadata: { error: `error resolving revocation registry definition with id ${revocationRegistryId}: ${resolutionMetadata.error} ${resolutionMetadata.message}`, }, - revocationListMetadata: { + revocationStatusListMetadata: { didIndyNamespace: pool.didIndyNamespace, }, } @@ -480,14 +480,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { return { resolutionMetadata: {}, - revocationList: anonCredsRevocationListFromIndySdk( + revocationStatusList: anonCredsRevocationStatusListFromIndySdk( revocationRegistryId, revocationRegistryDefinition, revocationRegistryDelta, deltaTimestamp, isIssuanceByDefault ), - revocationListMetadata: { + revocationStatusListMetadata: { didIndyNamespace: pool.didIndyNamespace, }, } @@ -505,7 +505,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { error: 'notFound', message: `Error retrieving revocation registry delta '${revocationRegistryId}' from ledger, potentially revocation interval ends before revocation registry creation: ${error.message}`, }, - revocationListMetadata: {}, + revocationStatusListMetadata: {}, } } } diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index e472d1c1c4..2e6e63ccc0 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -11,6 +11,8 @@ import type { GetCredentialsForProofRequestReturn, AnonCredsRequestedCredentials, AnonCredsCredentialRequestMetadata, + CreateLinkSecretOptions, + CreateLinkSecretReturn, } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' import type { @@ -23,7 +25,7 @@ import type { IndyProofRequest, } from 'indy-sdk' -import { inject } from '@aries-framework/core' +import { injectable, inject, utils } from '@aries-framework/core' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' @@ -38,6 +40,7 @@ import { import { IndySdkRevocationService } from './IndySdkRevocationService' +@injectable() export class IndySdkHolderService implements AnonCredsHolderService { private indySdk: IndySdk private indyRevocationService: IndySdkRevocationService @@ -47,6 +50,31 @@ export class IndySdkHolderService implements AnonCredsHolderService { this.indyRevocationService = indyRevocationService } + public async createLinkSecret( + agentContext: AgentContext, + options: CreateLinkSecretOptions + ): Promise { + assertIndySdkWallet(agentContext.wallet) + + const linkSecretId = options.linkSecretId ?? utils.uuid() + + try { + await this.indySdk.proverCreateMasterSecret(agentContext.wallet.handle, linkSecretId) + + // We don't have the value for the link secret when using the indy-sdk so we can't return it. + return { + linkSecretId, + } + } catch (error) { + agentContext.config.logger.error(`Error creating link secret`, { + error, + linkSecretId, + }) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } + public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { const { credentialDefinitions, proofRequest, requestedCredentials, schemas } = options diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts index 96e9ef266a..ba6c2a1780 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts @@ -8,11 +8,11 @@ import type { CreateSchemaOptions, AnonCredsCredentialOffer, AnonCredsSchema, - AnonCredsCredentialDefinition, + CreateCredentialDefinitionReturn, } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' -import { AriesFrameworkError, inject } from '@aries-framework/core' +import { injectable, AriesFrameworkError, inject } from '@aries-framework/core' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' @@ -21,6 +21,7 @@ import { generateLegacyProverDidLikeString } from '../utils/proverDid' import { createTailsReader } from '../utils/tails' import { indySdkSchemaFromAnonCreds } from '../utils/transform' +@injectable() export class IndySdkIssuerService implements AnonCredsIssuerService { private indySdk: IndySdk @@ -50,7 +51,7 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { agentContext: AgentContext, options: CreateCredentialDefinitionOptions, metadata?: CreateCredentialDefinitionMetadata - ): Promise { + ): Promise { const { tag, supportRevocation, schema, issuerId, schemaId } = options if (!metadata) @@ -70,11 +71,13 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { ) return { - issuerId, - tag: credentialDefinition.tag, - schemaId, - type: 'CL', - value: credentialDefinition.value, + credentialDefinition: { + issuerId, + tag: credentialDefinition.tag, + schemaId, + type: 'CL', + value: credentialDefinition.value, + }, } } catch (error) { throw isIndyError(error) ? new IndySdkError(error) : error diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts index 4f7eb6ef42..30f78bcbff 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkRevocationService.ts @@ -1,6 +1,6 @@ import type { AnonCredsRevocationRegistryDefinition, - AnonCredsRevocationList, + AnonCredsRevocationStatusList, AnonCredsProofRequest, AnonCredsRequestedCredentials, AnonCredsCredentialInfo, @@ -50,8 +50,8 @@ export class IndySdkRevocationService { // Tails is already downloaded tailsFilePath: string definition: AnonCredsRevocationRegistryDefinition - revocationLists: { - [timestamp: string]: AnonCredsRevocationList + revocationStatusLists: { + [timestamp: string]: AnonCredsRevocationStatusList } } } @@ -106,18 +106,18 @@ export class IndySdkRevocationService { this.assertRevocationInterval(requestRevocationInterval) - const { definition, revocationLists, tailsFilePath } = revocationRegistries[revocationRegistryId] - // NOTE: we assume that the revocationLists have been added based on timestamps of the `to` query. On a higher level it means we'll find the - // most accurate revocation list for a given timestamp. It doesn't have to be that the revocationList is from the `to` timestamp however. - const revocationList = revocationLists[requestRevocationInterval.to] + const { definition, revocationStatusLists, tailsFilePath } = revocationRegistries[revocationRegistryId] + // NOTE: we assume that the revocationStatusLists have been added based on timestamps of the `to` query. On a higher level it means we'll find the + // most accurate revocation list for a given timestamp. It doesn't have to be that the revocationStatusList is from the `to` timestamp however. + const revocationStatusList = revocationStatusLists[requestRevocationInterval.to] const tails = await createTailsReader(agentContext, tailsFilePath) const revocationState = await this.indySdk.createRevocationState( tails, indySdkRevocationRegistryDefinitionFromAnonCreds(revocationRegistryId, definition), - indySdkRevocationDeltaFromAnonCreds(revocationList), - revocationList.timestamp, + indySdkRevocationDeltaFromAnonCreds(revocationStatusList), + revocationStatusList.timestamp, credentialRevocationId ) const timestamp = revocationState.timestamp diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts index d07a4ef1ef..3e76fc6bc9 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts @@ -1,7 +1,7 @@ import type { AnonCredsVerifierService, VerifyProofOptions } from '@aries-framework/anoncreds' import type { CredentialDefs, Schemas, RevocRegDefs, RevRegs, IndyProofRequest } from 'indy-sdk' -import { inject } from '@aries-framework/core' +import { inject, injectable } from '@aries-framework/core' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' @@ -13,6 +13,7 @@ import { indySdkSchemaFromAnonCreds, } from '../utils/transform' +@injectable() export class IndySdkVerifierService implements AnonCredsVerifierService { private indySdk: IndySdk @@ -53,7 +54,7 @@ export class IndySdkVerifierService implements AnonCredsVerifierService { const indyRevocationRegistries: RevRegs = {} for (const revocationRegistryDefinitionId in options.revocationStates) { - const { definition, revocationLists } = options.revocationStates[revocationRegistryDefinitionId] + const { definition, revocationStatusLists } = options.revocationStates[revocationRegistryDefinitionId] indyRevocationDefinitions[revocationRegistryDefinitionId] = indySdkRevocationRegistryDefinitionFromAnonCreds( revocationRegistryDefinitionId, definition @@ -64,10 +65,10 @@ export class IndySdkVerifierService implements AnonCredsVerifierService { // Also transform the revocation lists for the specified timestamps into the revocation registry // format Indy expects - for (const timestamp in revocationLists) { - const revocationList = revocationLists[timestamp] + for (const timestamp in revocationStatusLists) { + const revocationStatusList = revocationStatusLists[timestamp] indyRevocationRegistries[revocationRegistryDefinitionId][timestamp] = - indySdkRevocationRegistryFromAnonCreds(revocationList) + indySdkRevocationRegistryFromAnonCreds(revocationStatusList) } } diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts index 20b16fa0ff..7930bfb2fb 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts @@ -108,7 +108,7 @@ describe('transform', () => { test.todo( 'indySdkRevocationRegistryDefinitionFromAnonCreds should return a valid indy sdk revocation registry definition' ) - test.todo('anonCredsRevocationListFromIndySdk should return a valid anoncreds revocation list') + test.todo('anonCredsRevocationStatusListFromIndySdk should return a valid anoncreds revocation list') test.todo('indySdkRevocationRegistryFromAnonCreds should return a valid indy sdk revocation registry') test.todo('indySdkRevocationDeltaFromAnonCreds should return a valid indy sdk revocation delta') }) diff --git a/packages/indy-sdk/src/anoncreds/utils/transform.ts b/packages/indy-sdk/src/anoncreds/utils/transform.ts index a5ad8afd60..6a91928f70 100644 --- a/packages/indy-sdk/src/anoncreds/utils/transform.ts +++ b/packages/indy-sdk/src/anoncreds/utils/transform.ts @@ -1,6 +1,6 @@ import type { AnonCredsCredentialDefinition, - AnonCredsRevocationList, + AnonCredsRevocationStatusList, AnonCredsRevocationRegistryDefinition, AnonCredsSchema, } from '@aries-framework/anoncreds' @@ -92,13 +92,13 @@ export function indySdkRevocationRegistryDefinitionFromAnonCreds( } } -export function anonCredsRevocationListFromIndySdk( +export function anonCredsRevocationStatusListFromIndySdk( revocationRegistryDefinitionId: string, revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition, delta: RevocRegDelta, timestamp: number, isIssuanceByDefault: boolean -): AnonCredsRevocationList { +): AnonCredsRevocationStatusList { // 0 means unrevoked, 1 means revoked const defaultState = isIssuanceByDefault ? 0 : 1 @@ -124,25 +124,27 @@ export function anonCredsRevocationListFromIndySdk( } } -export function indySdkRevocationRegistryFromAnonCreds(revocationList: AnonCredsRevocationList): RevocReg { +export function indySdkRevocationRegistryFromAnonCreds(revocationStatusList: AnonCredsRevocationStatusList): RevocReg { return { ver: '1.0', value: { - accum: revocationList.currentAccumulator, + accum: revocationStatusList.currentAccumulator, }, } } -export function indySdkRevocationDeltaFromAnonCreds(revocationList: AnonCredsRevocationList): RevocRegDelta { - // Get all indices from the revocationList that are revoked (so have value '1') - const revokedIndices = revocationList.revocationList.reduce( +export function indySdkRevocationDeltaFromAnonCreds( + revocationStatusList: AnonCredsRevocationStatusList +): RevocRegDelta { + // Get all indices from the revocationStatusList that are revoked (so have value '1') + const revokedIndices = revocationStatusList.revocationList.reduce( (revoked, current, index) => (current === 1 ? [...revoked, index] : revoked), [] ) return { value: { - accum: revocationList.currentAccumulator, + accum: revocationStatusList.currentAccumulator, issued: [], revoked: revokedIndices, // NOTE: I don't think this is used? From 7f65ba999ad1f49065d24966a1d7f3b82264ea55 Mon Sep 17 00:00:00 2001 From: Jim Ezesinachi Date: Mon, 6 Feb 2023 22:27:03 +0100 Subject: [PATCH 2/2] feat: optional routing for legacy connectionless invitation (#1271) Signed-off-by: Jim Ezesinachi --- packages/core/src/modules/oob/OutOfBandApi.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/core/src/modules/oob/OutOfBandApi.ts b/packages/core/src/modules/oob/OutOfBandApi.ts index aee58655da..5f1e0c00b7 100644 --- a/packages/core/src/modules/oob/OutOfBandApi.ts +++ b/packages/core/src/modules/oob/OutOfBandApi.ts @@ -247,9 +247,10 @@ export class OutOfBandApi { recordId: string message: Message domain: string + routing?: Routing }): Promise<{ message: Message; invitationUrl: string }> { // Create keys (and optionally register them at the mediator) - const routing = await this.routingService.getRouting(this.agentContext) + const routing = config.routing ?? (await this.routingService.getRouting(this.agentContext)) // Set the service on the message config.message.service = new ServiceDecorator({