From df3777ee394211a401940bf27b3e5a9e1688f6b2 Mon Sep 17 00:00:00 2001 From: Mo <10432473+morrieinmaas@users.noreply.github.com> Date: Fri, 7 Oct 2022 12:43:08 +0200 Subject: [PATCH] feat: add indynamespace for ledger id for anoncreds (#965) Signed-off-by: Moriarty --- .../setup-postgres-wallet-plugin/action.yml | 4 ++ Dockerfile | 3 + demo/src/BaseAgent.ts | 1 + docs/getting-started/ledger.md | 2 + .../indy/IndyCredentialFormatService.ts | 1 + .../dids/__tests__/dids-registrar.e2e.test.ts | 3 +- .../dids/methods/sov/SovDidRegistrar.ts | 7 +- .../sov/__tests__/SovDidRegistrar.test.ts | 6 +- .../AnonCredsCredentialDefinitionRecord.ts | 16 ++--- .../indy/repository/AnonCredsSchemaRecord.ts | 4 ++ packages/core/src/modules/ledger/IndyPool.ts | 6 ++ packages/core/src/modules/ledger/LedgerApi.ts | 67 ++++++++++++++++--- .../__tests__/IndyLedgerService.test.ts | 3 +- .../ledger/__tests__/IndyPoolService.test.ts | 8 ++- .../ledger/__tests__/LedgerApi.test.ts | 67 +++++++++++++------ .../ledger/__tests__/ledgerUtils.test.ts | 16 ----- .../core/src/modules/ledger/ledgerUtil.ts | 8 --- .../ledger/services/IndyLedgerService.ts | 4 ++ .../utils/__tests__/indyIdentifiers.test.ts | 62 +++++++++++++++++ packages/core/src/utils/index.ts | 1 + packages/core/src/utils/indyIdentifiers.ts | 53 +++++++++++++++ packages/core/tests/helpers.ts | 2 + packages/core/tests/ledger.test.ts | 1 + packages/module-tenants/src/TenantsModule.ts | 3 +- 24 files changed, 269 insertions(+), 79 deletions(-) create mode 100644 packages/core/src/utils/__tests__/indyIdentifiers.test.ts create mode 100644 packages/core/src/utils/indyIdentifiers.ts diff --git a/.github/actions/setup-postgres-wallet-plugin/action.yml b/.github/actions/setup-postgres-wallet-plugin/action.yml index 7ac41af866..a03b2f3fde 100644 --- a/.github/actions/setup-postgres-wallet-plugin/action.yml +++ b/.github/actions/setup-postgres-wallet-plugin/action.yml @@ -5,11 +5,15 @@ author: 'sairanjit.tummalapalli@ayanworks.com' runs: using: composite steps: + # cargo build failing on latest release of rust due to + # socket2 dependency in the plugin https://users.rust-lang.org/t/build-broken-with-parse-quote-spanned-is-ambiguous/80280/2 + # so pointing rust version to 1.63.0 - name: Setup Postgres wallet plugin run: | sudo apt-get install -y libzmq3-dev libsodium-dev pkg-config libssl-dev curl https://sh.rustup.rs -sSf | bash -s -- -y export PATH="/root/.cargo/bin:${PATH}" + rustup default 1.63.0 cd ../ git clone https://github.com/hyperledger/indy-sdk.git cd indy-sdk/experimental/plugins/postgres_storage/ diff --git a/Dockerfile b/Dockerfile index fa261eea80..91ccda0363 100644 --- a/Dockerfile +++ b/Dockerfile @@ -35,6 +35,9 @@ RUN apt-get install -y --no-install-recommends yarn RUN curl https://sh.rustup.rs -sSf | bash -s -- -y ENV PATH="/root/.cargo/bin:${PATH}" +# cargo build failing on latest release of rust due to socket2 dependency in the plugin https://users.rust-lang.org/t/build-broken-with-parse-quote-spanned-is-ambiguous/80280/2 so pointing rust version to 1.63.0 +RUN rustup default 1.63.0 + # clone indy-sdk and build postgres plugin RUN git clone https://github.com/hyperledger/indy-sdk.git WORKDIR /indy-sdk/experimental/plugins/postgres_storage/ diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index efc4260103..abf507014e 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -31,6 +31,7 @@ export class BaseAgent { { genesisTransactions: bcovrin, id: 'greenlights' + name, + indyNamespace: 'greenlights' + name, isProduction: false, }, ], diff --git a/docs/getting-started/ledger.md b/docs/getting-started/ledger.md index 2cc976c083..a79308d53b 100644 --- a/docs/getting-started/ledger.md +++ b/docs/getting-started/ledger.md @@ -18,11 +18,13 @@ const agentConfig: InitConfig = { indyLedgers: [ { id: 'sovrin-main', + didIndyNamespace: 'sovrin', isProduction: true, genesisPath: './genesis/sovrin-main.txn', }, { id: 'bcovrin-test', + didIndyNamespace: 'bcovrin:test', isProduction: false, genesisTransactions: 'XXXX', }, diff --git a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts index 491cde0be0..2a29055c1d 100644 --- a/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts +++ b/packages/core/src/modules/credentials/formats/indy/IndyCredentialFormatService.ts @@ -232,6 +232,7 @@ export class IndyCredentialFormatService extends CredentialFormatService { qualifiedIndyDid: `did:indy:localhost:${indyDid}`, }, didRegistrationMetadata: { - indyNamespace: 'localhost', + didIndyNamespace: 'localhost', }, didState: { state: 'finished', diff --git a/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts b/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts index cf5350ccbd..a1d83176b1 100644 --- a/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts +++ b/packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts @@ -102,9 +102,8 @@ export class SovDidRegistrar implements DidRegistrar { // Build did document. const didDocument = didDocumentBuilder.build() - // FIXME: we need to update this to the `indyNamespace` once https://github.com/hyperledger/aries-framework-javascript/issues/944 has been resolved - const indyNamespace = this.indyPoolService.ledgerWritePool.config.id - const qualifiedIndyDid = `did:indy:${indyNamespace}:${unqualifiedIndyDid}` + const didIndyNamespace = this.indyPoolService.ledgerWritePool.config.indyNamespace + const qualifiedIndyDid = `did:indy:${didIndyNamespace}:${unqualifiedIndyDid}` // Save the did so we know we created it and can issue with it const didRecord = new DidRecord({ @@ -122,7 +121,7 @@ export class SovDidRegistrar implements DidRegistrar { qualifiedIndyDid, }, didRegistrationMetadata: { - indyNamespace, + didIndyNamespace, }, didState: { state: 'finished', diff --git a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts b/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts index e2a3041652..073b67a3e8 100644 --- a/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts +++ b/packages/core/src/modules/dids/methods/sov/__tests__/SovDidRegistrar.test.ts @@ -21,7 +21,7 @@ const IndyLedgerServiceMock = IndyLedgerService as jest.Mock jest.mock('../../../../ledger/services/IndyPoolService') const IndyPoolServiceMock = IndyPoolService as jest.Mock const indyPoolServiceMock = new IndyPoolServiceMock() -mockProperty(indyPoolServiceMock, 'ledgerWritePool', { config: { id: 'pool1' } } as IndyPool) +mockProperty(indyPoolServiceMock, 'ledgerWritePool', { config: { id: 'pool1', indyNamespace: 'pool1' } } as IndyPool) const agentConfig = getAgentConfig('SovDidRegistrar') @@ -148,7 +148,7 @@ describe('DidRegistrar', () => { qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', }, didRegistrationMetadata: { - indyNamespace: 'pool1', + didIndyNamespace: 'pool1', }, didState: { state: 'finished', @@ -222,7 +222,7 @@ describe('DidRegistrar', () => { qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', }, didRegistrationMetadata: { - indyNamespace: 'pool1', + didIndyNamespace: 'pool1', }, didState: { state: 'finished', diff --git a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts index 730262447f..699abb6148 100644 --- a/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts +++ b/packages/core/src/modules/indy/repository/AnonCredsCredentialDefinitionRecord.ts @@ -1,28 +1,23 @@ import type { CredDef } from 'indy-sdk' import { BaseRecord } from '../../../storage/BaseRecord' -import { didFromCredentialDefinitionId } from '../../../utils/did' +import { uuid } from '../../../utils/uuid' export interface AnonCredsCredentialDefinitionRecordProps { credentialDefinition: CredDef } -export type DefaultAnonCredsCredentialDefinitionTags = { - credentialDefinitionId: string - issuerDid: string - schemaId: string - tag: string -} - -export class AnonCredsCredentialDefinitionRecord extends BaseRecord { +export class AnonCredsCredentialDefinitionRecord extends BaseRecord { public static readonly type = 'AnonCredsCredentialDefinitionRecord' public readonly type = AnonCredsCredentialDefinitionRecord.type + public readonly credentialDefinition!: CredDef public constructor(props: AnonCredsCredentialDefinitionRecordProps) { super() if (props) { + this.id = uuid() this.credentialDefinition = props.credentialDefinition } } @@ -31,9 +26,6 @@ export class AnonCredsCredentialDefinitionRecord extends BaseRecord { public static readonly type = 'AnonCredsSchemaRecord' public readonly type = AnonCredsSchemaRecord.type + public readonly schema!: Schema public constructor(props: AnonCredsSchemaRecordProps) { super() if (props) { + this.id = props.id ?? uuid() this.schema = props.schema } } diff --git a/packages/core/src/modules/ledger/IndyPool.ts b/packages/core/src/modules/ledger/IndyPool.ts index a6bd99c6e0..853b253b0e 100644 --- a/packages/core/src/modules/ledger/IndyPool.ts +++ b/packages/core/src/modules/ledger/IndyPool.ts @@ -1,6 +1,7 @@ import type { AgentDependencies } from '../../agent/AgentDependencies' import type { Logger } from '../../logger' import type { FileSystem } from '../../storage/FileSystem' +import type { DidIndyNamespace } from '../../utils/indyIdentifiers' import type * as Indy from 'indy-sdk' import type { Subject } from 'rxjs' @@ -20,6 +21,7 @@ export interface IndyPoolConfig { genesisTransactions?: string id: string isProduction: boolean + indyNamespace: DidIndyNamespace transactionAuthorAgreement?: TransactionAuthorAgreement } @@ -52,6 +54,10 @@ export class IndyPool { }) } + public get didIndyNamespace(): string { + return this.didIndyNamespace + } + public get id() { return this.poolConfig.id } diff --git a/packages/core/src/modules/ledger/LedgerApi.ts b/packages/core/src/modules/ledger/LedgerApi.ts index 07a535ff47..1cf2deef4f 100644 --- a/packages/core/src/modules/ledger/LedgerApi.ts +++ b/packages/core/src/modules/ledger/LedgerApi.ts @@ -7,11 +7,18 @@ import { AriesFrameworkError } from '../../error' import { IndySdkError } from '../../error/IndySdkError' import { injectable } from '../../plugins' import { isIndyError } from '../../utils/indyError' +import { + getLegacyCredentialDefinitionId, + getLegacySchemaId, + getQualifiedIndyCredentialDefinitionId, + getQualifiedIndySchemaId, +} from '../../utils/indyIdentifiers' +import { AnonCredsCredentialDefinitionRecord } from '../indy/repository/AnonCredsCredentialDefinitionRecord' import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository' +import { AnonCredsSchemaRecord } from '../indy/repository/AnonCredsSchemaRecord' import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository' import { LedgerModuleConfig } from './LedgerModuleConfig' -import { generateCredentialDefinitionId, generateSchemaId } from './ledgerUtil' import { IndyLedgerService } from './services' @injectable() @@ -73,16 +80,33 @@ export class LedgerApi { throw new AriesFrameworkError('Agent has no public DID.') } - const schemaId = generateSchemaId(did, schema.name, schema.version) + const schemaId = getLegacySchemaId(did, schema.name, schema.version) + + // Generate the qualified ID + const qualifiedIdentifier = getQualifiedIndySchemaId(this.ledgerService.getDidIndyWriteNamespace(), schemaId) // Try find the schema in the wallet - const schemaRecord = await this.anonCredsSchemaRepository.findBySchemaId(this.agentContext, schemaId) - // Schema in wallet - if (schemaRecord) return schemaRecord.schema + const schemaRecord = await this.anonCredsSchemaRepository.findById(this.agentContext, qualifiedIdentifier) + // Schema in wallet + if (schemaRecord) { + // Transform qualified to unqualified + return { + ...schemaRecord.schema, + id: schemaId, + } + } const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId) + if (schemaFromLedger) return schemaFromLedger - return this.ledgerService.registerSchema(this.agentContext, did, schema) + const createdSchema = await this.ledgerService.registerSchema(this.agentContext, did, schema) + + const anonCredsSchema = new AnonCredsSchemaRecord({ + schema: { ...createdSchema, id: qualifiedIdentifier }, + }) + await this.anonCredsSchemaRepository.save(this.agentContext, anonCredsSchema) + + return createdSchema } private async findBySchemaIdOnLedger(schemaId: string) { @@ -115,18 +139,32 @@ export class LedgerApi { } // Construct credential definition ID - const credentialDefinitionId = generateCredentialDefinitionId( + const credentialDefinitionId = getLegacyCredentialDefinitionId( did, credentialDefinitionTemplate.schema.seqNo, credentialDefinitionTemplate.tag ) + // Construct qualified identifier + const qualifiedIdentifier = getQualifiedIndyCredentialDefinitionId( + this.ledgerService.getDidIndyWriteNamespace(), + credentialDefinitionId + ) + // Check if the credential exists in wallet. If so, return it - const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId( + const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findById( this.agentContext, - credentialDefinitionId + qualifiedIdentifier ) - if (credentialDefinitionRecord) return credentialDefinitionRecord.credentialDefinition + + // Credential Definition in wallet + if (credentialDefinitionRecord) { + // Transform qualified to unqualified + return { + ...credentialDefinitionRecord.credentialDefinition, + id: credentialDefinitionId, + } + } // Check for the credential on the ledger. const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId) @@ -137,10 +175,17 @@ export class LedgerApi { } // Register the credential - return await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { + const registeredDefinition = await this.ledgerService.registerCredentialDefinition(this.agentContext, did, { ...credentialDefinitionTemplate, signatureType: 'CL', }) + // Replace the unqualified with qualified Identifier in anonCred + const anonCredCredential = new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: { ...registeredDefinition, id: qualifiedIdentifier }, + }) + await this.anonCredsCredentialDefinitionRepository.save(this.agentContext, anonCredCredential) + + return registeredDefinition } public async getCredentialDefinition(id: string) { diff --git a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts index 74636089b3..84563e5344 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyLedgerService.test.ts @@ -23,7 +23,8 @@ const CacheRepositoryMock = CacheRepository as jest.Mock const pools: IndyPoolConfig[] = [ { - id: 'sovrinMain', + id: 'sovrin', + indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, diff --git a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts index 3e6a65b96b..1cdae2af73 100644 --- a/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts +++ b/packages/core/src/modules/ledger/__tests__/IndyPoolService.test.ts @@ -24,30 +24,35 @@ const CacheRepositoryMock = CacheRepository as jest.Mock const pools: IndyPoolConfig[] = [ { id: 'sovrinMain', + indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { id: 'sovrinBuilder', + indyNamespace: 'sovrin:builder', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { - id: 'sovrinStaging', + id: 'sovringStaging', + indyNamespace: 'sovrin:staging', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { id: 'indicioMain', + indyNamespace: 'indicio', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, { id: 'bcovrinTest', + indyNamespace: 'bcovrin:test', isProduction: false, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, @@ -280,6 +285,7 @@ describe('IndyPoolService', () => { const { pool } = await poolService.getPoolForDid(agentContext, did) expect(pool.config.id).toBe('sovrinBuilder') + expect(pool.config.indyNamespace).toBe('sovrin:builder') const cacheRecord = spy.mock.calls[0][1] expect(cacheRecord.entries.length).toBe(1) diff --git a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts index 1df7a5c120..5ca80f5fcd 100644 --- a/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts +++ b/packages/core/src/modules/ledger/__tests__/LedgerApi.test.ts @@ -6,6 +6,7 @@ import type * as Indy from 'indy-sdk' import { getAgentConfig, getAgentContext, mockFunction, mockProperty } from '../../../../tests/helpers' import { SigningProviderRegistry } from '../../../crypto/signing-provider' import { AriesFrameworkError } from '../../../error/AriesFrameworkError' +import { getLegacySchemaId, getLegacyCredentialDefinitionId } from '../../../utils' import { IndyWallet } from '../../../wallet/IndyWallet' import { AnonCredsCredentialDefinitionRecord } from '../../indy/repository/AnonCredsCredentialDefinitionRecord' import { AnonCredsCredentialDefinitionRepository } from '../../indy/repository/AnonCredsCredentialDefinitionRepository' @@ -13,7 +14,6 @@ import { AnonCredsSchemaRecord } from '../../indy/repository/AnonCredsSchemaReco import { AnonCredsSchemaRepository } from '../../indy/repository/AnonCredsSchemaRepository' import { LedgerApi } from '../LedgerApi' import { LedgerModuleConfig } from '../LedgerModuleConfig' -import { generateCredentialDefinitionId, generateSchemaId } from '../ledgerUtil' import { IndyLedgerService } from '../services/IndyLedgerService' jest.mock('../services/IndyLedgerService') @@ -27,7 +27,7 @@ const AnonCredsSchemaRepositoryMock = AnonCredsSchemaRepository as jest.Mock = { - schema: schema, + schema: { ...schema, id: schemaIdQualified }, tag: 'someTag', supportRevocation: true, } @@ -78,9 +82,7 @@ const revocRegDef: Indy.RevocRegDef = { ver: 'abcde', } -const schemaIdGenerated = generateSchemaId(did, schema.name, schema.version) - -const credentialDefinitionId = generateCredentialDefinitionId( +const credentialDefinitionId = getLegacyCredentialDefinitionId( did, credentialDefinitionTemplate.schema.seqNo, credentialDefinitionTemplate.tag @@ -88,7 +90,8 @@ const credentialDefinitionId = generateCredentialDefinitionId( const pools: IndyPoolConfig[] = [ { - id: 'sovrinMain', + id: '7Tqg6BwSSWapxgUDm9KKgg', + indyNamespace: 'sovrin', isProduction: true, genesisTransactions: 'xxx', transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, @@ -210,11 +213,17 @@ describe('LedgerApi', () => { it('should return the schema from anonCreds when it already exists', async () => { mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) - mockFunction(anonCredsSchemaRepository.findBySchemaId).mockResolvedValueOnce( - new AnonCredsSchemaRecord({ schema: schema }) + mockFunction(anonCredsSchemaRepository.findById).mockResolvedValueOnce( + new AnonCredsSchemaRecord({ schema: { ...schema, id: schemaIdQualified } }) ) - await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual(schema) - expect(anonCredsSchemaRepository.findBySchemaId).toHaveBeenCalledWith(agentContext, schemaIdGenerated) + mockFunction(ledgerService.getDidIndyWriteNamespace).mockReturnValueOnce(pools[0].indyNamespace) + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { id, ...schemaWithoutId } = schema + await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toMatchObject({ + ...schema, + id: schema.id, + }) + expect(anonCredsSchemaRepository.findById).toHaveBeenCalledWith(agentContext, schemaIdQualified) }) it('should return the schema from the ledger when it already exists', async () => { @@ -223,9 +232,13 @@ describe('LedgerApi', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any .spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger') .mockResolvedValueOnce(new AnonCredsSchemaRecord({ schema: schema })) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toHaveProperty( 'schema', - schema + { ...schema } ) // eslint-disable-next-line @typescript-eslint/no-explicit-any expect(jest.spyOn(LedgerApi.prototype as any, 'findBySchemaIdOnLedger')).toHaveBeenCalledWith(schemaIdGenerated) @@ -234,6 +247,10 @@ describe('LedgerApi', () => { it('should return the schema after registering it', async () => { mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) mockFunction(ledgerService.registerSchema).mockResolvedValueOnce(schema) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) await expect(ledgerApi.registerSchema({ ...schema, attributes: ['hello', 'world'] })).resolves.toEqual(schema) expect(ledgerService.registerSchema).toHaveBeenCalledWith(agentContext, did, { ...schema, @@ -256,17 +273,19 @@ describe('LedgerApi', () => { new AnonCredsCredentialDefinitionRecord({ credentialDefinition: credDef, }) - mockFunction(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).mockResolvedValueOnce( + mockFunction(anonCredsCredentialDefinitionRepository.findById).mockResolvedValueOnce( anonCredsCredentialDefinitionRecord ) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) + mockFunction(ledgerService.getDidIndyWriteNamespace).mockReturnValueOnce(pools[0].indyNamespace) await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toHaveProperty( 'value.primary', credentialDefinition ) - expect(anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId).toHaveBeenCalledWith( - agentContext, - credentialDefinitionId - ) + expect(anonCredsCredentialDefinitionRepository.findById).toHaveBeenCalledWith(agentContext, qualifiedDidCred) }) it('should throw an exception if the definition already exists on the ledger', async () => { @@ -275,6 +294,10 @@ describe('LedgerApi', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any .spyOn(LedgerApi.prototype as any, 'findByCredentialDefinitionIdOnLedger') .mockResolvedValueOnce({ credentialDefinition: credentialDefinition }) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).rejects.toThrowError( AriesFrameworkError ) @@ -287,6 +310,10 @@ describe('LedgerApi', () => { it('should register the credential successfully if it is neither in the wallet and neither on the ledger', async () => { mockProperty(wallet, 'publicDid', { did: did, verkey: 'abcde' }) mockFunction(ledgerService.registerCredentialDefinition).mockResolvedValueOnce(credDef) + mockProperty(ledgerApi, 'config', { + connectToIndyLedgersOnStartup: true, + indyLedgers: pools, + } as LedgerModuleConfig) await expect(ledgerApi.registerCredentialDefinition(credentialDefinitionTemplate)).resolves.toEqual(credDef) expect(ledgerService.registerCredentialDefinition).toHaveBeenCalledWith(agentContext, did, { ...credentialDefinitionTemplate, diff --git a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts index a27e788ed2..ec33976a63 100644 --- a/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts +++ b/packages/core/src/modules/ledger/__tests__/ledgerUtils.test.ts @@ -42,20 +42,4 @@ describe('LedgerUtils', () => { } expect(LedgerUtil.isLedgerReqnackResponse(ledgerResponse)).toEqual(false) }) - - // generateSchemaId - it('Should return a valid schema ID given did name and version', () => { - const did = '12345', - name = 'backbench', - version = '420' - expect(LedgerUtil.generateSchemaId(did, name, version)).toEqual('12345:2:backbench:420') - }) - - // generateCredentialDefinitionId - it('Should return a valid schema ID given did name and version', () => { - const did = '12345', - seqNo = 420, - tag = 'someTag' - expect(LedgerUtil.generateCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') - }) }) diff --git a/packages/core/src/modules/ledger/ledgerUtil.ts b/packages/core/src/modules/ledger/ledgerUtil.ts index 6c9cfb1cf8..62e75f1e72 100644 --- a/packages/core/src/modules/ledger/ledgerUtil.ts +++ b/packages/core/src/modules/ledger/ledgerUtil.ts @@ -7,11 +7,3 @@ export function isLedgerRejectResponse(response: Indy.LedgerResponse): response export function isLedgerReqnackResponse(response: Indy.LedgerResponse): response is Indy.LedgerReqnackResponse { return response.op === 'REQNACK' } - -export function generateSchemaId(did: string, name: string, version: string) { - return `${did}:2:${name}:${version}` -} - -export function generateCredentialDefinitionId(did: string, seqNo: number, tag: string) { - return `${did}:3:CL:${seqNo}:${tag}` -} diff --git a/packages/core/src/modules/ledger/services/IndyLedgerService.ts b/packages/core/src/modules/ledger/services/IndyLedgerService.ts index 1e7916a84a..c4c3f4a0f6 100644 --- a/packages/core/src/modules/ledger/services/IndyLedgerService.ts +++ b/packages/core/src/modules/ledger/services/IndyLedgerService.ts @@ -51,6 +51,10 @@ export class IndyLedgerService { return this.indyPoolService.setPools(poolConfigs) } + public getDidIndyWriteNamespace(): string { + return this.indyPoolService.ledgerWritePool.config.indyNamespace + } + public async connectToPools() { return this.indyPoolService.connectToPools() } diff --git a/packages/core/src/utils/__tests__/indyIdentifiers.test.ts b/packages/core/src/utils/__tests__/indyIdentifiers.test.ts new file mode 100644 index 0000000000..8da274a789 --- /dev/null +++ b/packages/core/src/utils/__tests__/indyIdentifiers.test.ts @@ -0,0 +1,62 @@ +import { + isQualifiedIndyIdentifier, + getQualifiedIndyCredentialDefinitionId, + getQualifiedIndySchemaId, + getLegacyCredentialDefinitionId, + getLegacySchemaId, +} from '../indyIdentifiers' + +const indyNamespace = 'some:staging' +const did = 'q7ATwTYbQDgiigVijUAej' +const qualifiedSchemaId = `did:indy:${indyNamespace}:${did}/anoncreds/v0/SCHEMA/awesomeSchema/4.2.0` +const qualifiedCredentialDefinitionId = `did:indy:${indyNamespace}:${did}/anoncreds/v0/CLAIM_DEF/99/sth` +const unqualifiedSchemaId = `${did}:2:awesomeSchema:4.2.0` +const unqualifiedCredentialDefinitionId = `${did}:3:CL:99:sth` + +describe('Mangle indy identifiers', () => { + test('is a qualified identifier', async () => { + expect(isQualifiedIndyIdentifier(qualifiedSchemaId)).toBe(true) + }) + + test('is NOT a qualified identifier', async () => { + expect(isQualifiedIndyIdentifier(did)).toBe(false) + }) + + describe('get the qualified identifier', () => { + it('should return the qualified identifier if the identifier is already qualified', () => { + expect(getQualifiedIndyCredentialDefinitionId(indyNamespace, qualifiedCredentialDefinitionId)).toBe( + qualifiedCredentialDefinitionId + ) + }) + + it('should return the qualified identifier for a credential definition', () => { + expect(getQualifiedIndyCredentialDefinitionId(indyNamespace, unqualifiedCredentialDefinitionId)).toBe( + qualifiedCredentialDefinitionId + ) + }) + + it('should return the qualified identifier for a schema', () => { + expect(getQualifiedIndySchemaId(indyNamespace, qualifiedSchemaId)).toBe(qualifiedSchemaId) + }) + + it('should return the qualified identifier for a schema', () => { + expect(getQualifiedIndySchemaId(indyNamespace, unqualifiedSchemaId)).toBe(qualifiedSchemaId) + }) + }) + + // generateSchemaId + it('Should return a valid schema ID given did name and version', () => { + const did = '12345', + name = 'backbench', + version = '420' + expect(getLegacySchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + // generateCredentialDefinitionId + it('Should return a valid schema ID given did name and version', () => { + const did = '12345', + seqNo = 420, + tag = 'someTag' + expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) +}) diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 95ebc0b554..cb4f5d92e7 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -11,3 +11,4 @@ export * from './VarintEncoder' export * from './Hasher' export * from './validators' export * from './type' +export * from './indyIdentifiers' diff --git a/packages/core/src/utils/indyIdentifiers.ts b/packages/core/src/utils/indyIdentifiers.ts new file mode 100644 index 0000000000..0d6343a3e7 --- /dev/null +++ b/packages/core/src/utils/indyIdentifiers.ts @@ -0,0 +1,53 @@ +/** + * + * @see For the definitions below see also: https://hyperledger.github.io/indy-did-method/#indy-did-method-identifiers + * + */ +export type Did = 'did' +export type DidIndyMethod = 'indy' +// Maybe this can be typed more strictly than string. Choosing string for now as this can be eg just `sovrin` or eg `sovrin:staging` +export type DidIndyNamespace = string +// NOTE: because of the ambiguous nature - whether there is a colon or not within DidIndyNamespace this is the substring after the ***last*** colon +export type NamespaceIdentifier = string + +// TODO: This template literal type can possibly be improved. This version leaves the substrings as potentially undefined +export type IndyNamespace = `${Did}:${DidIndyMethod}:${DidIndyNamespace}:${NamespaceIdentifier}` + +export function isQualifiedIndyIdentifier(identifier: string | undefined): identifier is IndyNamespace { + if (!identifier || identifier === '') return false + return identifier.startsWith('did:indy:') +} + +export function getQualifiedIndyCredentialDefinitionId( + indyNamespace: string, + unqualifiedCredentialDefinitionId: string +): IndyNamespace { + if (isQualifiedIndyIdentifier(unqualifiedCredentialDefinitionId)) return unqualifiedCredentialDefinitionId + + // 5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npbd + const [did, , , seqNo, tag] = unqualifiedCredentialDefinitionId.split(':') + + return `did:indy:${indyNamespace}:${did}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` +} + +/** + * + * @see https://hyperledger.github.io/indy-did-method/#schema + * + */ +export function getQualifiedIndySchemaId(indyNamespace: string, schemaId: string): IndyNamespace { + if (isQualifiedIndyIdentifier(schemaId)) return schemaId + + // F72i3Y3Q4i466efjYJYCHM:2:npdb:4.3.4 + const [did, , schemaName, schemaVersion] = schemaId.split(':') + + return `did:indy:${indyNamespace}:${did}/anoncreds/v0/SCHEMA/${schemaName}/${schemaVersion}` +} + +export function getLegacySchemaId(unqualifiedDid: string, name: string, version: string) { + return `${unqualifiedDid}:2:${name}:${version}` +} + +export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: number, tag: string) { + return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` +} diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index f23e24dbc5..6378f64600 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -88,6 +88,7 @@ export function getAgentOptions(name: string, extraConfig: Partial = id: `pool-${name}`, isProduction: false, genesisPath, + indyNamespace: `pool:localtest`, transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' }, }, ], @@ -126,6 +127,7 @@ export function getPostgresAgentOptions(name: string, extraConfig: Partial { indyLedgers: [ { id: 'pool-Faber Ledger Genesis Transactions', + indyNamespace: 'pool-faber-ledger-genesis-transactions', isProduction: false, genesisTransactions, }, diff --git a/packages/module-tenants/src/TenantsModule.ts b/packages/module-tenants/src/TenantsModule.ts index 0d4880d61a..c96dbf92db 100644 --- a/packages/module-tenants/src/TenantsModule.ts +++ b/packages/module-tenants/src/TenantsModule.ts @@ -1,6 +1,5 @@ import type { TenantsModuleConfigOptions } from './TenantsModuleConfig' -import type { ModulesMap, DependencyManager, Module, EmptyModuleMap } from '@aries-framework/core' -import type { Constructor } from '@aries-framework/core' +import type { ModulesMap, DependencyManager, Module, EmptyModuleMap, Constructor } from '@aries-framework/core' import { InjectionSymbols } from '@aries-framework/core'