From 0e81028296e2bce6321db74a91e7189b2610be36 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sat, 25 Feb 2023 16:21:53 +0100 Subject: [PATCH 01/11] feat(indy-sdk)!: full did:indy support Signed-off-by: Timo Glastra --- .../anoncreds/tests/legacyAnonCredsSetup.ts | 7 +- ...f.credentials.propose-offerED25519.test.ts | 7 +- .../services/IndySdkAnonCredsRegistry.ts | 242 ++++----- .../utils/__tests__/identifiers.test.ts | 180 +++++-- .../src/anoncreds/utils/identifiers.ts | 160 ++++-- .../indy-sdk/src/anoncreds/utils/transform.ts | 29 +- .../src/dids/IndySdkIndyDidRegistrar.ts | 315 +++++++++++ .../src/dids/IndySdkIndyDidResolver.ts | 122 +++++ .../src/dids/IndySdkSovDidRegistrar.ts | 303 ----------- .../src/dids/IndySdkSovDidResolver.ts | 20 +- .../__tests__/IndySdkIndyDidRegistrar.test.ts | 494 ++++++++++++++++++ .../__tests__/IndySdkIndyDidResolver.test.ts | 127 +++++ .../__tests__/IndySdkSovDidRegistrar.test.ts | 372 ------------- .../didIndyPool1R1xKJw17sUoXhejEpugMYJ.json | 50 ++ .../didIndyPool1WJz9mHyW9BZksioQnRsrAo.json | 48 ++ packages/indy-sdk/src/dids/didIndyUtil.ts | 68 +++ packages/indy-sdk/src/dids/didSovUtil.ts | 6 + packages/indy-sdk/src/dids/index.ts | 8 +- packages/indy-sdk/src/index.ts | 8 +- .../indy-sdk/src/ledger/IndySdkPoolService.ts | 39 +- .../__tests__/IndySdkPoolService.test.ts | 6 + .../indy-sdk/src/utils/__tests__/did.test.ts | 12 +- packages/indy-sdk/src/utils/did.ts | 7 +- .../indy-sdk/tests/__fixtures__/anoncreds.ts | 30 ++ ...test.ts => indy-did-registrar.e2e.test.ts} | 50 +- .../tests/indy-did-resolver.e2e.test.ts | 99 ++++ .../indy-sdk-anoncreds-registry.e2e.test.ts | 309 ++++++++--- packages/indy-sdk/tests/setupIndySdkModule.ts | 12 +- .../tests/sov-did-resolver.e2e.test.ts | 50 +- 29 files changed, 2129 insertions(+), 1051 deletions(-) create mode 100644 packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts create mode 100644 packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts delete mode 100644 packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts create mode 100644 packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts create mode 100644 packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidResolver.test.ts delete mode 100644 packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts create mode 100644 packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json create mode 100644 packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json create mode 100644 packages/indy-sdk/src/dids/didIndyUtil.ts create mode 100644 packages/indy-sdk/tests/__fixtures__/anoncreds.ts rename packages/indy-sdk/tests/{sov-did-registrar.e2e.test.ts => indy-did-registrar.e2e.test.ts} (68%) create mode 100644 packages/indy-sdk/tests/indy-did-resolver.e2e.test.ts diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 32a876fd1a..a2b5e339ac 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -48,8 +48,9 @@ import { import testLogger from '../../core/tests/logger' import { IndySdkAnonCredsRegistry, + IndySdkIndyDidRegistrar, + IndySdkIndyDidResolver, IndySdkModule, - IndySdkSovDidRegistrar, IndySdkSovDidResolver, } from '../../indy-sdk/src' import { getIndySdkModuleConfig } from '../../indy-sdk/tests/setupIndySdkModule' @@ -98,8 +99,8 @@ export const getLegacyAnonCredsModules = ({ registries: [new IndySdkAnonCredsRegistry()], }), dids: new DidsModule({ - resolvers: [new IndySdkSovDidResolver()], - registrars: [new IndySdkSovDidRegistrar()], + resolvers: [new IndySdkSovDidResolver(), new IndySdkIndyDidResolver()], + registrars: [new IndySdkIndyDidRegistrar()], }), indySdk: new IndySdkModule(getIndySdkModuleConfig()), cache: new CacheModule({ diff --git a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts index 8c0956a145..6c514127ce 100644 --- a/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts +++ b/packages/core/src/modules/credentials/protocol/v2/__tests__/v2.ldproof.credentials.propose-offerED25519.test.ts @@ -12,8 +12,9 @@ import { import { prepareForAnonCredsIssuance } from '../../../../../../../anoncreds/tests/legacyAnonCredsSetup' import { IndySdkAnonCredsRegistry, + IndySdkIndyDidRegistrar, + IndySdkIndyDidResolver, IndySdkModule, - IndySdkSovDidRegistrar, IndySdkSovDidResolver, } from '../../../../../../../indy-sdk/src' import { indySdk } from '../../../../../../../indy-sdk/tests/setupIndySdkModule' @@ -107,8 +108,8 @@ const getIndyJsonLdModules = () => registries: [new IndySdkAnonCredsRegistry()], }), dids: new DidsModule({ - resolvers: [new IndySdkSovDidResolver(), new KeyDidResolver()], - registrars: [new IndySdkSovDidRegistrar(), new KeyDidRegistrar()], + resolvers: [new IndySdkSovDidResolver(), new IndySdkIndyDidResolver(), new KeyDidResolver()], + registrars: [new IndySdkIndyDidRegistrar(), new KeyDidRegistrar()], }), indySdk: new IndySdkModule({ indySdk, diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index 8e97b63748..fe0d30e35b 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -1,3 +1,4 @@ +import type { IndySdkPool } from '../../ledger' import type { IndySdk } from '../../types' import type { AnonCredsRegistry, @@ -13,23 +14,22 @@ import type { import type { AgentContext } from '@aries-framework/core' import type { Schema as IndySdkSchema } from 'indy-sdk' -import { DidsApi, getKeyDidMappingByVerificationMethod } from '@aries-framework/core' - +import { parseIndyDid, verificationKeyForIndyDid } from '../../dids/didIndyUtil' import { IndySdkError, isIndyError } from '../../error' import { IndySdkPoolService } from '../../ledger' import { IndySdkSymbol } from '../../types' import { - didFromCredentialDefinitionId, - didFromRevocationRegistryDefinitionId, - didFromSchemaId, + getDidIndyCredentialDefinitionId, + getDidIndySchemaId, getLegacyCredentialDefinitionId, + getLegacyRevocationRegistryId, getLegacySchemaId, indySdkAnonCredsRegistryIdentifierRegex, + parseCredentialDefinitionId, + parseRevocationRegistryId, + parseSchemaId, } from '../utils/identifiers' -import { - anonCredsRevocationStatusListFromIndySdk, - anonCredsRevocationRegistryDefinitionFromIndySdk, -} from '../utils/transform' +import { anonCredsRevocationStatusListFromIndySdk } from '../utils/transform' /** * TODO: validation of the identifiers. The Indy SDK classes only support the legacy (unqualified) identifiers. @@ -47,11 +47,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const did = didFromSchemaId(schemaId) + // parse schema id (supports did:indy and legacy) + const { did, didIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`) - const request = await indySdk.buildGetSchemaRequest(null, schemaId) + // even though we support did:indy and legacy identifiers we always need to fetch using the legacy identifier + const legacySchemaId = getLegacySchemaId(didIdentifier, schemaName, schemaVersion) + const request = await indySdk.buildGetSchemaRequest(null, legacySchemaId) agentContext.config.logger.trace( `Submitting get schema request for schema '${schemaId}' to ledger '${pool.didIndyNamespace}'` @@ -67,16 +70,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { schema, }) - const issuerId = didFromSchemaId(schema.id) - return { schema: { attrNames: schema.attrNames, name: schema.name, version: schema.version, - issuerId: issuerId, + issuerId: did, }, - schemaId: schema.id, + schemaId, resolutionMetadata: {}, schemaMetadata: { didIndyNamespace: pool.didIndyNamespace, @@ -104,63 +105,37 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { public async registerSchema( agentContext: AgentContext, - options: IndySdkRegisterSchemaOptions + options: RegisterSchemaOptions ): Promise { - // Make sure didIndyNamespace is passed - if (!options.options.didIndyNamespace) { - return { - schemaMetadata: {}, - registrationMetadata: {}, - schemaState: { - reason: 'no didIndyNamespace defined in the options. didIndyNamespace is required when using the Indy SDK', - schema: options.schema, - state: 'failed', - }, - } - } - try { + // This will throw an error if trying to register a schema with a legacy indy identifier. We only support did:indy identifiers + // for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { id: unqualifiedDid, namespace } = parseIndyDid(options.schema.issuerId) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const pool = indySdkPoolService.getPoolForNamespace(options.options.didIndyNamespace) + const pool = indySdkPoolService.getPoolForNamespace(namespace) agentContext.config.logger.debug( `Register schema on ledger '${pool.didIndyNamespace}' with did '${options.schema.issuerId}'`, options.schema ) + const didIndySchemaId = getDidIndySchemaId(namespace, unqualifiedDid, options.schema.name, options.schema.version) + const legacySchemaId = getLegacySchemaId(unqualifiedDid, options.schema.name, options.schema.version) + const schema = { attrNames: options.schema.attrNames, name: options.schema.name, version: options.schema.version, - id: getLegacySchemaId(options.schema.issuerId, options.schema.name, options.schema.version), + id: legacySchemaId, ver: '1.0', // Casted as because the type expect a seqNo, but that's not actually required for the input of // buildSchemaRequest (seqNo is not yet known) } as IndySdkSchema - const request = await indySdk.buildSchemaRequest(options.schema.issuerId, schema) - - // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did - // from the ledger to know which key is associated with the did - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didResult = await didsApi.resolve(`did:sov:${options.schema.issuerId}`) - - if (!didResult.didDocument) { - return { - schemaMetadata: {}, - registrationMetadata: {}, - schemaState: { - schema: options.schema, - state: 'failed', - reason: `didNotFound: unable to resolve did did:sov:${options.schema.issuerId}: ${didResult.didResolutionMetadata.message}`, - }, - } - } - - const verificationMethod = didResult.didDocument.dereferenceKey(`did:sov:${options.schema.issuerId}#key-1`) - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) - const submitterKey = getKeyFromVerificationMethod(verificationMethod) + const request = await indySdk.buildSchemaRequest(unqualifiedDid, schema) + const submitterKey = await verificationKeyForIndyDid(agentContext, options.schema.issuerId) const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterKey) agentContext.config.logger.debug(`Registered schema '${schema.id}' on ledger '${pool.didIndyNamespace}'`, { @@ -177,14 +152,13 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { name: schema.name, version: schema.version, }, - schemaId: schema.id, + schemaId: didIndySchemaId, }, registrationMetadata: {}, 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: response.result.txnMetadata.seqNo, - didIndyNamespace: pool.didIndyNamespace, }, } } catch (error) { @@ -214,13 +188,16 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const did = didFromCredentialDefinitionId(credentialDefinitionId) + // we support did:indy and legacy identifiers + const { did, didIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve credential definition '${credentialDefinitionId}'` ) - const request = await indySdk.buildGetCredDefRequest(null, credentialDefinitionId) + + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, tag) + const request = await indySdk.buildGetCredDefRequest(null, legacyCredentialDefinitionId) agentContext.config.logger.trace( `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.didIndyNamespace}'` @@ -235,7 +212,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ) const [, credentialDefinition] = await indySdk.parseGetCredDefResponse(response) - const { schema } = await this.fetchIndySchemaWithSeqNo(agentContext, Number(credentialDefinition.schemaId), did) + const { schema } = await this.fetchIndySchemaWithSeqNo(agentContext, pool, Number(credentialDefinition.schemaId)) if (credentialDefinition && schema) { agentContext.config.logger.debug( @@ -245,11 +222,16 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } ) + // Format the schema id based on the type of the credential definition id + const schemaId = credentialDefinitionId.startsWith('did:indy') + ? getDidIndySchemaId(pool.didIndyNamespace, didIdentifier, schema.name, schema.version) + : schema.schemaId + return { - credentialDefinitionId: credentialDefinition.id, + credentialDefinitionId, credentialDefinition: { - issuerId: didFromCredentialDefinitionId(credentialDefinition.id), - schemaId: schema.schemaId, + issuerId: did, + schemaId, tag: credentialDefinition.tag, type: 'CL', value: credentialDefinition.value, @@ -292,31 +274,23 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { public async registerCredentialDefinition( agentContext: AgentContext, - options: IndySdkRegisterCredentialDefinitionOptions + options: RegisterCredentialDefinitionOptions ): Promise { - // Make sure didIndyNamespace is passed - if (!options.options.didIndyNamespace) { - return { - credentialDefinitionMetadata: {}, - registrationMetadata: {}, - credentialDefinitionState: { - reason: 'no didIndyNamespace defined in the options. didIndyNamespace is required when using the Indy SDK', - credentialDefinition: options.credentialDefinition, - state: 'failed', - }, - } - } - try { + // This will throw an error if trying to register a credential defintion with a legacy indy identifier. We only support did:indy + // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { id: unqualifiedDid, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const pool = indySdkPoolService.getPoolForNamespace(options.options.didIndyNamespace) + const pool = indySdkPoolService.getPoolForNamespace(namespace) agentContext.config.logger.debug( `Registering credential definition on ledger '${pool.didIndyNamespace}' with did '${options.credentialDefinition.issuerId}'`, options.credentialDefinition ) + // TODO: check structure of the schemaId // TODO: this will bypass caching if done on a higher level. const { schema, schemaMetadata, resolutionMetadata } = await this.getSchema( agentContext, @@ -337,14 +311,20 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } } - const credentialDefinitionId = getLegacyCredentialDefinitionId( - options.credentialDefinition.issuerId, + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( + unqualifiedDid, + schemaMetadata.indyLedgerSeqNo, + options.credentialDefinition.tag + ) + const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + unqualifiedDid, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag ) - const request = await indySdk.buildCredDefRequest(options.credentialDefinition.issuerId, { - id: credentialDefinitionId, + const request = await indySdk.buildCredDefRequest(unqualifiedDid, { + id: legacyCredentialDefinitionId, // Indy ledger requires the credential schemaId to be a string of the schema seqNo. schemaId: schemaMetadata.indyLedgerSeqNo.toString(), tag: options.credentialDefinition.tag, @@ -353,33 +333,12 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ver: '1.0', }) - // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did - // from the ledger to know which key is associated with the did - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didResult = await didsApi.resolve(`did:sov:${options.credentialDefinition.issuerId}`) - - if (!didResult.didDocument) { - return { - credentialDefinitionMetadata: {}, - registrationMetadata: {}, - credentialDefinitionState: { - credentialDefinition: options.credentialDefinition, - state: 'failed', - reason: `didNotFound: unable to resolve did did:sov:${options.credentialDefinition.issuerId}: ${didResult.didResolutionMetadata.message}`, - }, - } - } - - const verificationMethod = didResult.didDocument.dereferenceKey( - `did:sov:${options.credentialDefinition.issuerId}#key-1` - ) - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) - const submitterKey = getKeyFromVerificationMethod(verificationMethod) + const submitterKey = await verificationKeyForIndyDid(agentContext, options.credentialDefinition.issuerId) const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterKey) agentContext.config.logger.debug( - `Registered credential definition '${credentialDefinitionId}' on ledger '${pool.didIndyNamespace}'`, + `Registered credential definition '${didIndyCredentialDefinitionId}' on ledger '${pool.didIndyNamespace}'`, { response, credentialDefinition: options.credentialDefinition, @@ -387,12 +346,10 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ) return { - credentialDefinitionMetadata: { - didIndyNamespace: pool.didIndyNamespace, - }, + credentialDefinitionMetadata: {}, credentialDefinitionState: { credentialDefinition: options.credentialDefinition, - credentialDefinitionId, + credentialDefinitionId: didIndyCredentialDefinitionId, state: 'finished', }, registrationMetadata: {}, @@ -419,13 +376,21 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) + const { did, didIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = + parseRevocationRegistryId(revocationRegistryDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` ) - const request = await indySdk.buildGetRevocRegDefRequest(null, revocationRegistryDefinitionId) + + const legacyRevocationRegistryId = getLegacyRevocationRegistryId( + didIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + const request = await indySdk.buildGetRevocRegDefRequest(null, legacyRevocationRegistryId) agentContext.config.logger.trace( `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` @@ -447,9 +412,24 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } ) + const credentialDefinitionId = revocationRegistryDefinitionId.startsWith('did:indy:') + ? getDidIndyCredentialDefinitionId(pool.didIndyNamespace, didIdentifier, schemaSeqNo, credentialDefinitionTag) + : getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, credentialDefinitionTag) + return { resolutionMetadata: {}, - revocationRegistryDefinition: anonCredsRevocationRegistryDefinitionFromIndySdk(revocationRegistryDefinition), + revocationRegistryDefinition: { + issuerId: did, + credDefId: credentialDefinitionId, + value: { + maxCredNum: revocationRegistryDefinition.value.maxCredNum, + publicKeys: revocationRegistryDefinition.value.publicKeys, + tailsHash: revocationRegistryDefinition.value.tailsHash, + tailsLocation: revocationRegistryDefinition.value.tailsLocation, + }, + tag: revocationRegistryDefinition.tag, + revocDefType: 'CL_ACCUM', + }, revocationRegistryDefinitionId, revocationRegistryDefinitionMetadata: { issuanceType: revocationRegistryDefinition.value.issuanceType, @@ -485,15 +465,23 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const did = didFromRevocationRegistryDefinitionId(revocationRegistryId) + const { did, didIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + parseRevocationRegistryId(revocationRegistryId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` ) + const legacyRevocationRegistryId = getLegacyRevocationRegistryId( + didIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) + // TODO: implement caching for returned deltas - const request = await indySdk.buildGetRevocRegDeltaRequest(null, revocationRegistryId, 0, timestamp) + const request = await indySdk.buildGetRevocRegDeltaRequest(null, legacyRevocationRegistryId, 0, timestamp) agentContext.config.logger.trace( `Submitting get revocation registry delta request for revocation registry '${revocationRegistryId}' to ledger` @@ -569,14 +557,13 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } } - private async fetchIndySchemaWithSeqNo(agentContext: AgentContext, seqNo: number, did: string) { + private async fetchIndySchemaWithSeqNo(agentContext: AgentContext, pool: IndySdkPool, seqNo: number) { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting transaction with seqNo '${seqNo}' from ledger '${pool.didIndyNamespace}'`) - const request = await indySdk.buildGetTxnRequest(did, 'DOMAIN', seqNo) + const request = await indySdk.buildGetTxnRequest(null, 'DOMAIN', seqNo) agentContext.config.logger.trace(`Submitting get transaction request to ledger '${pool.didIndyNamespace}'`) const response = await indySdkPoolService.submitReadRequest(pool, request) @@ -588,15 +575,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { return {} } - const schemaId = getLegacySchemaId(did, schema.txn.data.data.name, schema.txn.data.data.version) - return { schema: { - schemaId, + // txnId is the schema id + schemaId: schema.txnMetadata.txnId, attr_name: schema.txn.data.data.attr_names, name: schema.txn.data.data.name, version: schema.txn.data.data.version, - issuerId: did, + issuerId: schema.txn.metadata.from, seqNo, }, indyNamespace: pool.didIndyNamespace, @@ -605,7 +591,13 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } interface SchemaType { + txnMetadata: { + txnId: string + } txn: { + metadata: { + from: string + } data: { data: { attr_names: string[] @@ -617,15 +609,3 @@ interface SchemaType { type: string } } - -export interface IndySdkRegisterSchemaOptions extends RegisterSchemaOptions { - options: { - didIndyNamespace: string - } -} - -export interface IndySdkRegisterCredentialDefinitionOptions extends RegisterCredentialDefinitionOptions { - options: { - didIndyNamespace: string - } -} diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts index 76454b615a..4dba3a8e17 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -1,31 +1,53 @@ import { - didFromSchemaId, - didFromCredentialDefinitionId, - didFromRevocationRegistryDefinitionId, - getIndySeqNoFromUnqualifiedCredentialDefinitionId, + getDidIndyCredentialDefinitionId, + getDidIndyRevocationRegistryId, + getDidIndySchemaId, getLegacyCredentialDefinitionId, + getLegacyRevocationRegistryId, getLegacySchemaId, indySdkAnonCredsRegistryIdentifierRegex, + parseCredentialDefinitionId, + parseRevocationRegistryId, + parseSchemaId, } from '../identifiers' describe('identifiers', () => { - it('matches against a legacy indy did, schema id, credential definition id and revocation registry id', () => { - const did = '7Tqg6BwSSWapxgUDm9KKgg' - const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' - const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' - const revocationRegistryId = - 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' - - const anotherId = 'some:id' - - expect(indySdkAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) - expect(indySdkAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) - expect(indySdkAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) - expect(indySdkAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) - expect(indySdkAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + describe('indySdkAnonCredsRegistryIdentifierRegex', () => { + test('matches against a legacy indy did, schema id, credential definition id and revocation registry id', () => { + const did = '7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' + const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' + const revocationRegistryId = + 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + + const anotherId = 'some:id' + + expect(indySdkAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + + test('matches against a did indy did, schema id, credential definition id and revocation registry id', () => { + const did = 'did:indy:local:7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0' + const credentialDefinitionId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID' + const revocationRegistryId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/REV_REG_DEF/100669/SCH Employee ID/1-1024' + + const anotherId = 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/SOME_DEF' + + expect(indySdkAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) }) - it('getLegacySchemaId should return a valid schema id given a did, name, and version', () => { + test('getLegacySchemaId returns a valid schema id given a did, name, and version', () => { const did = '12345' const name = 'backbench' const version = '420' @@ -33,7 +55,7 @@ describe('identifiers', () => { expect(getLegacySchemaId(did, name, version)).toEqual('12345:2:backbench:420') }) - it('getLegacyCredentialDefinitionId should return a valid credential definition id given a did, seqNo, and tag', () => { + test('getLegacyCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { const did = '12345' const seqNo = 420 const tag = 'someTag' @@ -41,25 +63,121 @@ describe('identifiers', () => { expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') }) - it('getIndySeqNoFromUnqualifiedCredentialDefinitionId should return the seqNo from the credential definition id', () => { - expect(getIndySeqNoFromUnqualifiedCredentialDefinitionId('12345:3:CL:420:someTag')).toEqual(420) + test('getLegacyRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getLegacyRevocationRegistryId(did, seqNo, credentialDefinitionTag, tag)).toEqual( + '12345:4:12345:3:CL:420:someTag:CL_ACCUM:anotherTag' + ) + }) + + test('getDidIndySchemaId returns a valid schema id given a did, name, and version', () => { + const namespace = 'sovrin:test' + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getDidIndySchemaId(namespace, did, name, version)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/SCHEMA/backbench/420' + ) + }) + + test('getDidIndyCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getDidIndyCredentialDefinitionId(namespace, did, seqNo, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/CLAIM_DEF/420/someTag' + ) + }) + + test('getDidIndyRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getDidIndyRevocationRegistryId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/REV_REG_DEF/420/someTag/anotherTag' + ) }) - it('didFromSchemaId should return the did from the schema id', () => { - const schemaId = '12345:2:backbench:420' + describe('parseSchemaId', () => { + test('parses legacy schema id', () => { + expect(parseSchemaId('SDqTzbVuCowusqGBNbNDjH:2:schema-name:1.0')).toEqual({ + did: 'SDqTzbVuCowusqGBNbNDjH', + didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + schemaName: 'schema-name', + schemaVersion: '1.0', + }) + }) - expect(didFromSchemaId(schemaId)).toEqual('12345') + test('parses did:indy schema id', () => { + expect(parseSchemaId('did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0')).toEqual( + { + didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + did: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + schemaName: 'schema-name', + schemaVersion: '1.0', + namespace: 'bcovrin:test', + } + ) + }) }) - it('didFromCredentialDefinitionId should return the did from the credential definition id', () => { - const credentialDefinitionId = '12345:3:CL:420:someTag' + describe('parseCredentialDefinitionId', () => { + test('parses legacy credential definition id', () => { + expect(parseCredentialDefinitionId('TL1EaPFCZ8Si5aUrqScBDt:3:CL:10:TAG')).toEqual({ + did: 'TL1EaPFCZ8Si5aUrqScBDt', + didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + schemaSeqNo: '10', + tag: 'TAG', + }) + }) - expect(didFromCredentialDefinitionId(credentialDefinitionId)).toEqual('12345') + test('parses did:indy credential definition id', () => { + expect( + parseCredentialDefinitionId('did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/10/TAG') + ).toEqual({ + didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + did: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', + namespace: 'pool:localtest', + schemaSeqNo: '10', + tag: 'TAG', + }) + }) }) - it('didFromRevocationRegistryDefinitionId should return the did from the revocation registry id', () => { - const revocationRegistryId = '12345:3:CL:420:someTag' + describe('parseRevocationRegistryId', () => { + test('parses legacy revocation registry id', () => { + expect( + parseRevocationRegistryId('5nDyJVP1NrcPAttP3xwMB9:4:5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npdb:CL_ACCUM:TAG1') + ).toEqual({ + did: '5nDyJVP1NrcPAttP3xwMB9', + didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + schemaSeqNo: '56495', + credentialDefinitionTag: 'npdb', + revocationRegistryTag: 'TAG1', + }) + }) - expect(didFromRevocationRegistryDefinitionId(revocationRegistryId)).toEqual('12345') + test('parses did:indy revocation registry id', () => { + expect( + parseRevocationRegistryId('did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1') + ).toEqual({ + namespace: 'sovrin', + didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + did: 'did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9', + schemaSeqNo: '56495', + credentialDefinitionTag: 'npdb', + revocationRegistryTag: 'TAG1', + }) + }) }) }) diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index 62d2650602..d80ed1de8d 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -1,52 +1,150 @@ -export const legacyIndyIssuerIdRegex = /^[a-zA-Z0-9]{21,22}$/ -export const legacyIndySchemaIdRegex = /^[a-zA-Z0-9]{21,22}:2:.+:[0-9.]+$/ -export const legacyIndyCredentialDefinitionIdRegex = - /^[a-zA-Z0-9]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+)):(.+)?$/ -export const legacyIndyRevocationRegistryIdRegex = - /^[a-zA-Z0-9]{21,22}:4:[a-zA-Z0-9]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+))(:.+)?:CL_ACCUM:(.+$)/ +import { DID_INDY_REGEX } from '../../utils/did' -export const indySdkAnonCredsRegistryIdentifierRegex = new RegExp( - `${legacyIndyIssuerIdRegex.source}|${legacyIndySchemaIdRegex.source}|${legacyIndyCredentialDefinitionIdRegex.source}|${legacyIndyRevocationRegistryIdRegex.source}` +const legacyIndyIssuerIdRegex = /^[a-zA-Z0-9]{21,22}$/ + +const didIndyAnonCredsBase = + /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ + +// did:indy::/anoncreds/v0/SCHEMA// +const didIndySchemaIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/SCHEMA/(?.+)/(?[0-9.]+)$` +) + +// :2:: +const legacyIndySchemaIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ + +// did:indy::/anoncreds/v0/CLAIM_DEF// +const didIndyCredentialDefinitionIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/CLAIM_DEF/(?[1-9][0-9]*)/(?.+)$` +) + +// :3:CL:: +const legacyIndyCredentialDefinitionIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ + +// did:indy::/anoncreds/v0/REV_REG_DEF/// +const didIndyRevocationRegistryIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/REV_REG_DEF/(?[1-9][0-9]*)/(?.+)/(?.+)$` ) -export function getIndySeqNoFromUnqualifiedCredentialDefinitionId(unqualifiedCredentialDefinitionId: string): number { - // 5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npbd - const [, , , seqNo] = unqualifiedCredentialDefinitionId.split(':') +// :4::3:CL::CL_ACCUM: +const legacyIndyRevocationRegistryIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ + +// combines both legacy and did:indy anoncreds identifiers and also the issuer id +const indySdkAnonCredsRegexes = [ + // issuer id + legacyIndyIssuerIdRegex, + DID_INDY_REGEX, + + // schema + didIndySchemaIdRegex, + legacyIndySchemaIdRegex, + + // credential definition + didIndyCredentialDefinitionIdRegex, + legacyIndyCredentialDefinitionIdRegex, + + // revocation registry + legacyIndyRevocationRegistryIdRegex, + didIndyRevocationRegistryIdRegex, +] + +export const indySdkAnonCredsRegistryIdentifierRegex = new RegExp( + indySdkAnonCredsRegexes.map((r) => r.source.replace(/(\?<[a-zA-Z]+>)?/g, '')).join('|') +) - return Number(seqNo) +export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, name: string, version: string) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/SCHEMA/${name}/${version}` } export function getLegacySchemaId(unqualifiedDid: string, name: string, version: string) { return `${unqualifiedDid}:2:${name}:${version}` } -export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: number, tag: string) { +export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: string | number, tag: string) { return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` } -/** - * Extract did from schema id - */ -export function didFromSchemaId(schemaId: string) { - const [did] = schemaId.split(':') +export function getDidIndyCredentialDefinitionId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + tag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` +} + +// TZQuLp43UcYTdtc3HewcDz:4:TZQuLp43UcYTdtc3HewcDz:3:CL:98158:BaustellenzertifikateNU1:CL_ACCUM:1-100 +export function getLegacyRevocationRegistryId( + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `${unqualifiedDid}:4:${unqualifiedDid}:3:CL:${seqNo}:${credentialDefinitionTag}:CL_ACCUM:${revocationRegistryTag}` +} + +export function getDidIndyRevocationRegistryId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` +} - return did +interface ParsedSchemaId { + did: string + didIdentifier: string + schemaName: string + schemaVersion: string + namespace?: string } -/** - * Extract did from credential definition id - */ -export function didFromCredentialDefinitionId(credentialDefinitionId: string) { - const [did] = credentialDefinitionId.split(':') +export function parseSchemaId(schemaId: string) { + const match = schemaId.match(didIndySchemaIdRegex) ?? schemaId.match(legacyIndySchemaIdRegex) + + if (!match) throw new Error(`Invalid schema id: ${schemaId}`) + + return match.groups as unknown as ParsedSchemaId +} - return did +interface ParsedCredentialDefinitionId { + did: string + didIdentifier: string + schemaSeqNo: string + tag: string + namespace?: string } -/** - * Extract did from revocation registry definition id - */ -export function didFromRevocationRegistryDefinitionId(revocationRegistryId: string) { - const [did] = revocationRegistryId.split(':') +export function parseCredentialDefinitionId(credentialDefinitionId: string) { + const match = + credentialDefinitionId.match(didIndyCredentialDefinitionIdRegex) ?? + credentialDefinitionId.match(legacyIndyCredentialDefinitionIdRegex) + + if (!match) throw new Error(`Invalid credential definition id: ${credentialDefinitionId}`) + + return match.groups as unknown as ParsedCredentialDefinitionId +} + +interface ParsedRevocationRegistryId { + did: string + didIdentifier: string + schemaSeqNo: string + credentialDefinitionTag: string + revocationRegistryTag: string + namespace?: string +} + +export function parseRevocationRegistryId(revocationRegistryId: string) { + const match = + revocationRegistryId.match(didIndyRevocationRegistryIdRegex) ?? + revocationRegistryId.match(legacyIndyRevocationRegistryIdRegex) + + if (!match) throw new Error(`Invalid revocation registry id: ${revocationRegistryId}`) - return did + return match.groups as unknown as ParsedRevocationRegistryId } diff --git a/packages/indy-sdk/src/anoncreds/utils/transform.ts b/packages/indy-sdk/src/anoncreds/utils/transform.ts index e976d514e4..a993475349 100644 --- a/packages/indy-sdk/src/anoncreds/utils/transform.ts +++ b/packages/indy-sdk/src/anoncreds/utils/transform.ts @@ -6,12 +6,12 @@ import type { } from '@aries-framework/anoncreds' import type { CredDef, RevocReg, RevocRegDef, RevocRegDelta, Schema } from 'indy-sdk' -import { didFromCredentialDefinitionId, didFromRevocationRegistryDefinitionId, didFromSchemaId } from './identifiers' +import { parseCredentialDefinitionId, parseSchemaId } from './identifiers' export function anonCredsSchemaFromIndySdk(schema: Schema): AnonCredsSchema { - const issuerId = didFromSchemaId(schema.id) + const { did } = parseSchemaId(schema.id) return { - issuerId, + issuerId: did, name: schema.name, version: schema.version, attrNames: schema.attrNames, @@ -30,10 +30,10 @@ export function indySdkSchemaFromAnonCreds(schemaId: string, schema: AnonCredsSc } export function anonCredsCredentialDefinitionFromIndySdk(credentialDefinition: CredDef): AnonCredsCredentialDefinition { - const issuerId = didFromCredentialDefinitionId(credentialDefinition.id) + const { did } = parseCredentialDefinitionId(credentialDefinition.id) return { - issuerId, + issuerId: did, schemaId: credentialDefinition.schemaId, tag: credentialDefinition.tag, type: 'CL', @@ -55,25 +55,6 @@ export function indySdkCredentialDefinitionFromAnonCreds( } } -export function anonCredsRevocationRegistryDefinitionFromIndySdk( - revocationRegistryDefinition: RevocRegDef -): AnonCredsRevocationRegistryDefinition { - const issuerId = didFromRevocationRegistryDefinitionId(revocationRegistryDefinition.id) - - return { - issuerId, - credDefId: revocationRegistryDefinition.credDefId, - value: { - maxCredNum: revocationRegistryDefinition.value.maxCredNum, - publicKeys: revocationRegistryDefinition.value.publicKeys, - tailsHash: revocationRegistryDefinition.value.tailsHash, - tailsLocation: revocationRegistryDefinition.value.tailsLocation, - }, - tag: revocationRegistryDefinition.tag, - revocDefType: 'CL_ACCUM', - } -} - export function indySdkRevocationRegistryDefinitionFromAnonCreds( revocationRegistryDefinitionId: string, revocationRegistryDefinition: AnonCredsRevocationRegistryDefinition diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts new file mode 100644 index 0000000000..77689dcde6 --- /dev/null +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts @@ -0,0 +1,315 @@ +import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndySdkPool } from '../ledger' +import type { IndySdk } from '../types' +import type { + AgentContext, + Buffer, + DidCreateOptions, + DidCreateResult, + DidDeactivateResult, + DidRegistrar, + DidUpdateResult, +} from '@aries-framework/core' +import type { NymRole } from 'indy-sdk' + +import { DidDocumentRole, DidRecord, DidRepository, KeyType, Key } from '@aries-framework/core' + +import { IndySdkError } from '../error' +import { isIndyError } from '../error/indyError' +import { IndySdkPoolService } from '../ledger' +import { IndySdkSymbol } from '../types' +import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' +import { isLegacySelfCertifiedDid, legacyIndyDidFromPublicKeyBase58 } from '../utils/did' + +import { createKeyAgreementKey, indyDidDocumentFromDid, parseIndyDid, verificationKeyForIndyDid } from './didIndyUtil' +import { addServicesFromEndpointsAttrib } from './didSovUtil' + +export class IndySdkIndyDidRegistrar implements DidRegistrar { + public readonly supportedMethods = ['indy'] + + public async create(agentContext: AgentContext, options: IndySdkIndyDidCreateOptions): Promise { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const didRepository = agentContext.dependencyManager.resolve(DidRepository) + + const { alias, role, submitterDid, endpoints } = options.options + let did = options.did + let didIdentifier: string + let verificationKey: Key + const privateKey = options.secret?.privateKey + + if (did && privateKey) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Only one of 'privateKey' or 'did' must be provided`, + }, + } + } + + try { + assertIndySdkWallet(agentContext.wallet) + + // Parse submitterDid and extract namespace based on the submitter did + const { namespace: submitterNamespace, id: submitterDidIdentifier } = parseIndyDid(submitterDid) + const submitterSigningKey = await verificationKeyForIndyDid(agentContext, submitterDid) + + // Only supports version 1 did identifier (which is same as did:sov) + if (did) { + if (!options.options.verkey) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'If a did is defined, a matching verkey must be provided', + }, + } + } + + const { namespace, id } = parseIndyDid(did) + didIdentifier = id + verificationKey = Key.fromPublicKeyBase58(options.options.verkey, KeyType.Ed25519) + + if (!isLegacySelfCertifiedDid(didIdentifier, options.options.verkey)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Did must be first 16 bytes of the the verkey base58 encoded.`, + }, + } + } + + if (submitterNamespace !== namespace) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `The submitter did uses namespace ${submitterNamespace} and the did to register uses namespace ${namespace}. Namespaces must match.`, + }, + } + } + } else { + // Create a new key and calculate did according to the rules for indy did method + verificationKey = await agentContext.wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) + didIdentifier = legacyIndyDidFromPublicKeyBase58(verificationKey.publicKeyBase58) + did = `did:indy:${submitterNamespace}:${didIdentifier}` + } + + const pool = indySdkPoolService.getPoolForNamespace(submitterNamespace) + await this.registerPublicDid( + agentContext, + pool, + submitterDidIdentifier, + submitterSigningKey, + didIdentifier, + verificationKey, + alias, + role + ) + + // Create did document + const didDocumentBuilder = indyDidDocumentFromDid(did, verificationKey.publicKeyBase58) + + // Add services if endpoints object was passed. + if (endpoints) { + const keyAgreementId = `${did}#key-agreement-1` + + await this.setEndpointsForDid(agentContext, pool, didIdentifier, verificationKey, endpoints) + + didDocumentBuilder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: did, + id: keyAgreementId, + publicKeyBase58: createKeyAgreementKey(verificationKey.publicKeyBase58), + type: 'X25519KeyAgreementKey2019', + }) + .addKeyAgreement(keyAgreementId) + + // Process endpoint attrib following the same rules as for did:sov + addServicesFromEndpointsAttrib(didDocumentBuilder, did, endpoints, keyAgreementId) + } + + // Build did document. + const didDocument = didDocumentBuilder.build() + + // Save the did so we know we created it and can issue with it + const didRecord = new DidRecord({ + did, + role: DidDocumentRole.Created, + tags: { + recipientKeyFingerprints: didDocument.recipientKeys.map((key: Key) => key.fingerprint), + }, + }) + await didRepository.save(agentContext, didRecord) + + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did, + didDocument, + secret: { + // FIXME: the uni-registrar creates the seed in the registrar method + // if it doesn't exist so the seed can always be returned. Currently + // we can only return it if the seed was passed in by the user. Once + // we have a secure method for generating seeds we should use the same + // approach + privateKey: options.secret?.privateKey, + }, + }, + } + } catch (error) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `unknownError: ${error.message}`, + }, + } + } + } + + public async update(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:indy not implemented yet`, + }, + } + } + + public async deactivate(): Promise { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:indy not implemented yet`, + }, + } + } + + public async registerPublicDid( + agentContext: AgentContext, + pool: IndySdkPool, + unqualifiedSubmitterDid: string, + submitterSigningKey: Key, + unqualifiedDid: string, + signingKey: Key, + alias: string, + role?: NymRole + ) { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + try { + agentContext.config.logger.debug(`Register public did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`) + + const request = await indySdk.buildNymRequest( + unqualifiedSubmitterDid, + unqualifiedDid, + signingKey.publicKeyBase58, + alias, + role || null + ) + + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterSigningKey) + + agentContext.config.logger.debug( + `Registered public did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + { + response, + } + ) + } catch (error) { + agentContext.config.logger.error( + `Error registering public did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + { + error, + unqualifiedSubmitterDid, + unqualifiedDid, + verkey: signingKey.publicKeyBase58, + alias, + role, + pool: pool.didIndyNamespace, + } + ) + + throw error + } + } + + public async setEndpointsForDid( + agentContext: AgentContext, + pool: IndySdkPool, + unqualifiedDid: string, + signingKey: Key, + endpoints: IndyEndpointAttrib + ): Promise { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + try { + agentContext.config.logger.debug( + `Set endpoints for did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + endpoints + ) + + const request = await indySdk.buildAttribRequest( + unqualifiedDid, + unqualifiedDid, + null, + { endpoint: endpoints }, + null + ) + + const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, signingKey) + agentContext.config.logger.debug( + `Successfully set endpoints for did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + { + response, + endpoints, + } + ) + } catch (error) { + agentContext.config.logger.error( + `Error setting endpoints for did '${unqualifiedDid}' on ledger '${pool.didIndyNamespace}'`, + { + error, + unqualifiedDid, + endpoints, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} + +export interface IndySdkIndyDidCreateOptions extends DidCreateOptions { + method: 'indy' + did?: string + // The indy sdk can only publish a very limited did document (what is mostly known as a legacy did:sov did) and thus we require everything + // needed to construct the did document to be passed through the options object. + didDocument?: never + options: { + alias: string + role?: NymRole + verkey?: string + endpoints?: IndyEndpointAttrib + submitterDid: string + } + secret?: { + privateKey?: Buffer + } +} diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts new file mode 100644 index 0000000000..836ed8040b --- /dev/null +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts @@ -0,0 +1,122 @@ +import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndySdkPool } from '../ledger' +import type { IndySdk } from '../types' +import type { DidResolutionResult, DidResolver, AgentContext } from '@aries-framework/core' + +import { isIndyError, IndySdkError } from '../error' +import { IndySdkPoolService } from '../ledger/IndySdkPoolService' +import { IndySdkSymbol } from '../types' +import { getFullVerkey } from '../utils/did' + +import { createKeyAgreementKey, indyDidDocumentFromDid, parseIndyDid } from './didIndyUtil' +import { addServicesFromEndpointsAttrib } from './didSovUtil' + +export class IndySdkIndyDidResolver implements DidResolver { + public readonly supportedMethods = ['indy'] + + public async resolve(agentContext: AgentContext, did: string): Promise { + const didDocumentMetadata = {} + + try { + const { id: unqualifiedDid, namespace } = parseIndyDid(did) + + const poolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const pool = poolService.getPoolForNamespace(namespace) + + const nym = await this.getPublicDid(agentContext, pool, unqualifiedDid) + const endpoints = await this.getEndpointsForDid(agentContext, pool, unqualifiedDid) + + // For modern did:indy DIDs, we assume that GET_NYM is always a full verkey in base58. + // For backwards compatibility, we accept a shortened verkey and convert it using previous convention + const verkey = getFullVerkey(did, nym.verkey) + const builder = indyDidDocumentFromDid(did, verkey) + + // NOTE: we don't support the `diddocContent` field in the GET_NYM response using the indy-sdk. So if the did would have the `diddocContent` field + // we will ignore it without knowing if it is present. We may be able to extract the diddocContent from the GET_NYM response in the future, but need + // some dids registered with diddocContent to test with. + if (endpoints) { + const keyAgreementId = `${did}#key-agreement-1` + + builder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: did, + id: keyAgreementId, + publicKeyBase58: createKeyAgreementKey(verkey), + type: 'X25519KeyAgreementKey2019', + }) + .addKeyAgreement(keyAgreementId) + addServicesFromEndpointsAttrib(builder, did, endpoints, keyAgreementId) + } + + return { + didDocument: builder.build(), + didDocumentMetadata, + didResolutionMetadata: { contentType: 'application/did+ld+json' }, + } + } catch (error) { + return { + didDocument: null, + didDocumentMetadata, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did '${did}': ${error}`, + }, + } + } + } + + private async getPublicDid(agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string) { + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + + const request = await indySdk.buildGetNymRequest(null, unqualifiedDid) + const response = await indySdkPoolService.submitReadRequest(pool, request) + + return await indySdk.parseGetNymResponse(response) + } + + private async getEndpointsForDid(agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string) { + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) + const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + + try { + agentContext.config.logger.debug( + `Get endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'` + ) + + const request = await indySdk.buildGetAttribRequest(null, unqualifiedDid, 'endpoint', null, null) + + agentContext.config.logger.debug( + `Submitting get endpoint ATTRIB request for did '${unqualifiedDid}' to ledger '${pool.didIndyNamespace}'` + ) + const response = await indySdkPoolService.submitReadRequest(pool, request) + + if (!response.result.data) { + return null + } + + const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib + agentContext.config.logger.debug( + `Got endpoints '${JSON.stringify(endpoints)}' for did '${unqualifiedDid}' from ledger '${ + pool.didIndyNamespace + }'`, + { + response, + endpoints, + } + ) + + return endpoints + } catch (error) { + agentContext.config.logger.error( + `Error retrieving endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'`, + { + error, + } + ) + + throw isIndyError(error) ? new IndySdkError(error) : error + } + } +} diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts deleted file mode 100644 index c85cf7c0e0..0000000000 --- a/packages/indy-sdk/src/dids/IndySdkSovDidRegistrar.ts +++ /dev/null @@ -1,303 +0,0 @@ -import type { IndyEndpointAttrib } from './didSovUtil' -import type { IndySdkPool } from '../ledger' -import type { IndySdk } from '../types' -import type { - AgentContext, - DidRegistrar, - DidCreateOptions, - DidCreateResult, - DidDeactivateResult, - DidUpdateResult, - Buffer, - Key, -} from '@aries-framework/core' -import type { NymRole } from 'indy-sdk' - -import { - DidsApi, - getKeyDidMappingByVerificationMethod, - KeyType, - isValidPrivateKey, - DidDocumentRole, - DidRecord, - DidRepository, -} from '@aries-framework/core' - -import { IndySdkError } from '../error' -import { isIndyError } from '../error/indyError' -import { IndySdkPoolService } from '../ledger' -import { IndySdkSymbol } from '../types' -import { indyDidFromPublicKeyBase58 } from '../utils/did' - -import { addServicesFromEndpointsAttrib, sovDidDocumentFromDid } from './didSovUtil' - -export class IndySdkSovDidRegistrar implements DidRegistrar { - public readonly supportedMethods = ['sov'] - - public async create(agentContext: AgentContext, options: IndySdkSovDidCreateOptions): Promise { - const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) - const didRepository = agentContext.dependencyManager.resolve(DidRepository) - - const { alias, role, submitterVerificationMethod, indyNamespace } = options.options - const privateKey = options.secret?.privateKey - - if (privateKey && !isValidPrivateKey(privateKey, KeyType.Ed25519)) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid private key provided', - }, - } - } - - if (!submitterVerificationMethod.startsWith('did:sov:')) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Submitter did must be a valid did:sov did', - }, - } - } - - try { - const signingKey = await agentContext.wallet.createKey({ - privateKey, - keyType: KeyType.Ed25519, - }) - const verkey = signingKey.publicKeyBase58 - - const unqualifiedIndyDid = indyDidFromPublicKeyBase58(verkey) - - // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did - // from the ledger to know which key is associated with the did - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didResult = await didsApi.resolve(submitterVerificationMethod) - - if (!didResult.didDocument) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `didNotFound: unable to resolve did ${submitterVerificationMethod}: ${didResult.didResolutionMetadata.message}`, - }, - } - } - - const verificationMethod = didResult.didDocument.dereferenceKey(submitterVerificationMethod) - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) - const submitterSigningKey = getKeyFromVerificationMethod(verificationMethod) - - const qualifiedSovDid = `did:sov:${unqualifiedIndyDid}` - const [unqualifiedSubmitterDid] = submitterVerificationMethod.replace('did:sov:', '').split('#') - - const pool = indySdkPoolService.getPoolForNamespace(indyNamespace) - await this.registerPublicDid( - agentContext, - unqualifiedSubmitterDid, - submitterSigningKey, - unqualifiedIndyDid, - signingKey, - alias, - pool, - role - ) - - // Create did document - const didDocumentBuilder = sovDidDocumentFromDid(qualifiedSovDid, verkey) - - // Add services if endpoints object was passed. - if (options.options.endpoints) { - await this.setEndpointsForDid(agentContext, unqualifiedIndyDid, signingKey, options.options.endpoints, pool) - addServicesFromEndpointsAttrib( - didDocumentBuilder, - qualifiedSovDid, - options.options.endpoints, - `${qualifiedSovDid}#key-agreement-1` - ) - } - - // Build did document. - const didDocument = didDocumentBuilder.build() - - const didIndyNamespace = pool.config.indyNamespace - const qualifiedIndyDid = `did:indy:${didIndyNamespace}:${unqualifiedIndyDid}` - - // Save the did so we know we created it and can issue with it - const didRecord = new DidRecord({ - did: qualifiedSovDid, - role: DidDocumentRole.Created, - tags: { - recipientKeyFingerprints: didDocument.recipientKeys.map((key: Key) => key.fingerprint), - qualifiedIndyDid, - }, - }) - await didRepository.save(agentContext, didRecord) - - return { - didDocumentMetadata: { - qualifiedIndyDid, - }, - didRegistrationMetadata: { - didIndyNamespace, - }, - didState: { - state: 'finished', - did: qualifiedSovDid, - didDocument, - secret: { - // FIXME: the uni-registrar creates the seed in the registrar method - // if it doesn't exist so the seed can always be returned. Currently - // we can only return it if the seed was passed in by the user. Once - // we have a secure method for generating seeds we should use the same - // approach - privateKey: options.secret?.privateKey, - }, - }, - } - } catch (error) { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `unknownError: ${error.message}`, - }, - } - } - } - - public async update(): Promise { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: updating did:sov not implemented yet`, - }, - } - } - - public async deactivate(): Promise { - return { - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: deactivating did:sov not implemented yet`, - }, - } - } - - public async registerPublicDid( - agentContext: AgentContext, - submitterDid: string, - submitterSigningKey: Key, - targetDid: string, - signingKey: Key, - alias: string, - pool: IndySdkPool, - role?: NymRole - ) { - const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) - - try { - agentContext.config.logger.debug(`Register public did '${targetDid}' on ledger '${pool.didIndyNamespace}'`) - - const request = await indySdk.buildNymRequest( - submitterDid, - targetDid, - signingKey.publicKeyBase58, - alias, - role || null - ) - - const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterSigningKey) - - agentContext.config.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.didIndyNamespace}'`, { - response, - }) - - return targetDid - } catch (error) { - agentContext.config.logger.error( - `Error registering public did '${targetDid}' on ledger '${pool.didIndyNamespace}'`, - { - error, - submitterDid, - targetDid, - verkey: signingKey.publicKeyBase58, - alias, - role, - pool: pool.didIndyNamespace, - } - ) - - throw error - } - } - - public async setEndpointsForDid( - agentContext: AgentContext, - did: string, - signingKey: Key, - endpoints: IndyEndpointAttrib, - pool: IndySdkPool - ): Promise { - const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) - - try { - agentContext.config.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.didIndyNamespace}'`, endpoints) - - const request = await indySdk.buildAttribRequest(did, did, null, { endpoint: endpoints }, null) - - const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, signingKey) - agentContext.config.logger.debug( - `Successfully set endpoints for did '${did}' on ledger '${pool.didIndyNamespace}'`, - { - response, - endpoints, - } - ) - } catch (error) { - agentContext.config.logger.error( - `Error setting endpoints for did '${did}' on ledger '${pool.didIndyNamespace}'`, - { - error, - did, - endpoints, - } - ) - - throw isIndyError(error) ? new IndySdkError(error) : error - } - } -} - -export interface IndySdkSovDidCreateOptions extends DidCreateOptions { - method: 'sov' - did?: undefined - // As did:sov is so limited, we require everything needed to construct the did document to be passed - // through the options object. Once we support did:indy we can allow the didDocument property. - didDocument?: never - options: { - alias: string - role?: NymRole - endpoints?: IndyEndpointAttrib - indyNamespace?: string - submitterVerificationMethod: string - } - secret?: { - privateKey?: Buffer - } -} - -// Update and Deactivate not supported for did:sov -export type IndySdkSovDidUpdateOptions = never -export type IndySdkSovDidDeactivateOptions = never diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts index 98007e5166..bbbeec71f8 100644 --- a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts @@ -1,4 +1,5 @@ import type { IndyEndpointAttrib } from './didSovUtil' +import type { IndySdkPool } from '../ledger' import type { IndySdk } from '../types' import type { DidResolutionResult, ParsedDid, DidResolver, AgentContext } from '@aries-framework/core' @@ -15,8 +16,10 @@ export class IndySdkSovDidResolver implements DidResolver { const didDocumentMetadata = {} try { - const nym = await this.getPublicDid(agentContext, parsed.id) - const endpoints = await this.getEndpointsForDid(agentContext, parsed.id) + const poolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const { pool, nymResponse } = await poolService.getPoolForDid(agentContext, parsed.id) + const nym = nymResponse ?? (await this.getPublicDid(agentContext, pool, parsed.id)) + const endpoints = await this.getEndpointsForDid(agentContext, pool, parsed.id) const keyAgreementId = `${parsed.did}#key-agreement-1` const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) @@ -39,21 +42,20 @@ export class IndySdkSovDidResolver implements DidResolver { } } - private async getPublicDid(agentContext: AgentContext, did: string) { + private async getPublicDid(agentContext: AgentContext, pool: IndySdkPool, did: string) { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) + const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - // Getting the pool for a did also retrieves the DID. We can just use that - const { did: didResponse } = await indySdkPoolService.getPoolForDid(agentContext, did) + const request = await indySdk.buildGetNymRequest(null, did) + const response = await indySdkPoolService.submitReadRequest(pool, request) - return didResponse + return await indySdk.parseGetNymResponse(response) } - private async getEndpointsForDid(agentContext: AgentContext, did: string) { + private async getEndpointsForDid(agentContext: AgentContext, pool: IndySdkPool, did: string) { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) - const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) - try { agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.didIndyNamespace}'`) diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts new file mode 100644 index 0000000000..4d4390bd24 --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts @@ -0,0 +1,494 @@ +import type { IndySdkPool } from '../../ledger/IndySdkPool' +import type { DidRecord, RecordSavedEvent } from '@aries-framework/core' + +import { + SigningProviderRegistry, + DidsApi, + DidDocument, + VerificationMethod, + KeyType, + Key, + TypedArrayEncoder, + DidRepository, + JsonTransformer, + DidDocumentRole, + EventEmitter, + RepositoryEventTypes, +} from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { mockFunction, getAgentConfig, getAgentContext, agentDependencies, indySdk } from '../../../../core/tests' +import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' +import { IndySdkWallet } from '../../wallet' +import { IndySdkIndyDidRegistrar } from '../IndySdkIndyDidRegistrar' + +jest.mock('../../ledger/IndySdkPoolService') +const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock +const indySdkPoolServiceMock = new IndySdkPoolServiceMock() + +const pool = { + config: { indyNamespace: 'pool1' }, +} as IndySdkPool +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue(pool) + +const agentConfig = getAgentConfig('IndySdkIndyDidRegistrar') +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) + +jest + .spyOn(wallet, 'createKey') + .mockResolvedValue(Key.fromPublicKeyBase58('E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', KeyType.Ed25519)) +const storageService = new InMemoryStorageService() +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const didRepository = new DidRepository(storageService, eventEmitter) + +const agentContext = getAgentContext({ + wallet, + registerInstances: [ + [DidRepository, didRepository], + [IndySdkPoolService, indySdkPoolServiceMock], + [ + DidsApi, + { + resolve: jest.fn().mockResolvedValue({ + didDocument: new DidDocument({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + authentication: [ + new VerificationMethod({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }), + ], + }), + }), + }, + ], + ], + agentConfig, +}) + +const indySdkIndyDidRegistrar = new IndySdkIndyDidRegistrar() + +describe('IndySdkIndyDidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('returns an error state if both did and privateKey are provided', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + did: 'did:indy:pool1:did-value', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + privateKey: TypedArrayEncoder.fromString('key'), + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Only one of 'privateKey' or 'did' must be provided`, + }, + }) + }) + + test('returns an error state if the submitter did is not a valid did:indy did', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but it is not a valid did:indy did', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but no verkey', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'If a did is defined, a matching verkey must be provided', + }, + }) + }) + + test('returns an error state if did and verkey are provided, but the did is not self certifying', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + did: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Did must be first 16 bytes of the the verkey base58 encoded.', + }, + }) + }) + + test('returns an error state if did is provided, but does not match with the namespace from the submitterDid', async () => { + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + did: 'did:indy:pool2:R1xKJw17sUoXhejEpugMYJ', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: + 'The submitter did uses namespace pool1 and the did to register uses namespace pool2. Namespaces must match.', + }, + }) + }) + + test('creates a did:indy document without services', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: { + privateKey, + }, + }) + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + pool, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('creates a did:indy document by passing did', async () => { + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + options: { + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: {}, + }) + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + pool, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: {}, + }, + }) + }) + + test('creates a did:indy document with services', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const result = await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['DIDComm', 'did-communication', 'endpoint'], + }, + }, + secret: { + privateKey, + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + pool, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'R1xKJw17sUoXhejEpugMYJ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + verificationMethod: [ + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey'], + assertionMethod: undefined, + keyAgreement: ['did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('stores the did document', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const saveCalled = jest.fn() + eventEmitter.on>(RepositoryEventTypes.RecordSaved, saveCalled) + + await indySdkIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + endpoints: { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['DIDComm', 'did-communication', 'endpoint'], + }, + }, + secret: { + privateKey, + }, + }) + + expect(saveCalled).toHaveBeenCalledTimes(1) + const [saveEvent] = saveCalled.mock.calls[0] + + expect(saveEvent.payload.record).toMatchObject({ + did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], + }, + didDocument: undefined, + }) + }) + + test('returns an error state when calling update', async () => { + const result = await indySdkIndyDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:indy not implemented yet`, + }, + }) + }) + + test('returns an error state when calling deactivate', async () => { + const result = await indySdkIndyDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:indy not implemented yet`, + }, + }) + }) +}) diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidResolver.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidResolver.test.ts new file mode 100644 index 0000000000..7c4f294286 --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidResolver.test.ts @@ -0,0 +1,127 @@ +import type { IndySdkPool } from '../../ledger' +import type { IndyEndpointAttrib } from '../didSovUtil' +import type { GetNymResponse } from 'indy-sdk' + +import { SigningProviderRegistry, JsonTransformer } from '@aries-framework/core' +import indySdk from 'indy-sdk' + +import { mockFunction, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers' +import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' +import { IndySdkSymbol } from '../../types' +import { IndySdkWallet } from '../../wallet' +import { IndySdkIndyDidResolver } from '../IndySdkIndyDidResolver' + +import didIndyPool1R1xKJw17sUoXhejEpugMYJFixture from './__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json' +import didIndyPool1WJz9mHyW9BZksioQnRsrAoFixture from './__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json' + +jest.mock('../../ledger/IndySdkPoolService') +const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock +const indySdkPoolServiceMock = new IndySdkPoolServiceMock() + +mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ + config: { indyNamespace: 'pool1' }, +} as IndySdkPool) + +const agentConfig = getAgentConfig('IndySdkIndyDidResolver') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) + +const agentContext = getAgentContext({ + wallet, + agentConfig, + registerInstances: [ + [IndySdkPoolService, indySdkPoolServiceMock], + [IndySdkSymbol, indySdk], + ], +}) + +const indySdkSovDidResolver = new IndySdkIndyDidResolver() + +describe('IndySdkIndyDidResolver', () => { + it('should correctly resolve a did:indy document', async () => { + const did = 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ' + + const nymResponse: GetNymResponse = { + did: 'R1xKJw17sUoXhejEpugMYJ', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://ssi.com', + profile: 'https://profile.com', + hub: 'https://hub.com', + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didIndyPool1R1xKJw17sUoXhejEpugMYJFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should resolve a did:indy document with routingKeys and types entries in the attrib', async () => { + const did = 'did:indy:pool1:WJz9mHyW9BZksioQnRsrAo' + + const nymResponse: GetNymResponse = { + did: 'WJz9mHyW9BZksioQnRsrAo', + verkey: 'GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8', + role: 'ENDORSER', + } + + const endpoints: IndyEndpointAttrib = { + endpoint: 'https://agent.com', + types: ['endpoint', 'did-communication', 'DIDComm'], + routingKeys: ['routingKey1', 'routingKey2'], + } + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockResolvedValue(nymResponse) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getEndpointsForDid').mockResolvedValue(endpoints) + + const result = await indySdkSovDidResolver.resolve(agentContext, did) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocument: didIndyPool1WJz9mHyW9BZksioQnRsrAoFixture, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + it('should return did resolution metadata with error if the indy ledger service throws an error', async () => { + const did = 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ' + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + jest.spyOn(indySdkSovDidResolver, 'getPublicDid').mockRejectedValue(new Error('Error retrieving did')) + + const result = await indySdkSovDidResolver.resolve(agentContext, did) + + expect(result).toMatchObject({ + didDocument: null, + didDocumentMetadata: {}, + didResolutionMetadata: { + error: 'notFound', + message: `resolver_error: Unable to resolve did 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ': Error: Error retrieving did`, + }, + }) + }) +}) diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts deleted file mode 100644 index 2f63d2a91a..0000000000 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidRegistrar.test.ts +++ /dev/null @@ -1,372 +0,0 @@ -import type { IndySdkPool } from '../../ledger/IndySdkPool' -import type { Wallet, DidRecord, RecordSavedEvent } from '@aries-framework/core' - -import { - DidsApi, - DidDocument, - VerificationMethod, - KeyType, - Key, - TypedArrayEncoder, - DidRepository, - JsonTransformer, - DidDocumentRole, - EventEmitter, - RepositoryEventTypes, -} from '@aries-framework/core' -import { Subject } from 'rxjs' - -import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' -import { mockFunction, getAgentConfig, getAgentContext, agentDependencies } from '../../../../core/tests' -import { IndySdkPoolService } from '../../ledger/IndySdkPoolService' -import { IndySdkSovDidRegistrar } from '../IndySdkSovDidRegistrar' - -jest.mock('../../ledger/IndySdkPoolService') -const IndySdkPoolServiceMock = IndySdkPoolService as jest.Mock -const indySdkPoolServiceMock = new IndySdkPoolServiceMock() - -mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ - config: { indyNamespace: 'pool1' }, -} as IndySdkPool) - -const agentConfig = getAgentConfig('IndySdkSovDidRegistrar') - -const wallet = { - createKey: jest - .fn() - .mockResolvedValue(Key.fromPublicKeyBase58('E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', KeyType.Ed25519)), -} as unknown as Wallet -const storageService = new InMemoryStorageService() -const eventEmitter = new EventEmitter(agentDependencies, new Subject()) -const didRepository = new DidRepository(storageService, eventEmitter) - -const agentContext = getAgentContext({ - wallet, - registerInstances: [ - [DidRepository, didRepository], - [IndySdkPoolService, indySdkPoolServiceMock], - [ - DidsApi, - { - resolve: jest.fn().mockResolvedValue({ - didDocument: new DidDocument({ - id: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - authentication: [ - new VerificationMethod({ - id: 'did:sov:BzCbsNYhMrjHiqZDTUASHg#key-1', - type: 'Ed25519VerificationKey2018', - controller: 'did:sov:BzCbsNYhMrjHiqZDTUASHg', - publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - }), - ], - }), - }), - }, - ], - ], - agentConfig, -}) - -const indySdkSovDidRegistrar = new IndySdkSovDidRegistrar() - -describe('IndySdkSovDidRegistrar', () => { - afterEach(() => { - jest.clearAllMocks() - }) - - it('should return an error state if an invalid private key is provided', async () => { - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - - options: { - submitterVerificationMethod: 'did:sov:BzCbsNYhMrjHiqZDTUASHg#key-1', - alias: 'Hello', - }, - secret: { - privateKey: TypedArrayEncoder.fromString('invalid'), - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Invalid private key provided', - }, - }) - }) - - it('should return an error state if the submitter did is not qualified with did:sov', async () => { - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - submitterVerificationMethod: 'BzCbsNYhMrjHiqZDTUASHg', - alias: 'Hello', - }, - }) - - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: 'Submitter did must be a valid did:sov did', - }, - }) - }) - - it('should correctly create a did:sov document without services', async () => { - const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) - - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterVerificationMethod: 'did:sov:BzCbsNYhMrjHiqZDTUASHg#key-1', - role: 'STEWARD', - }, - secret: { - privateKey, - }, - }) - expect(registerPublicDidSpy).toHaveBeenCalledWith( - agentContext, - // Unqualified submitter did - 'BzCbsNYhMrjHiqZDTUASHg', - // submitter signing key, - expect.any(Key), - // Unqualified created indy did - 'R1xKJw17sUoXhejEpugMYJ', - // Verkey - expect.any(Key), - // Alias - 'Hello', - // Pool - { config: { indyNamespace: 'pool1' } }, - // Role - 'STEWARD' - ) - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didRegistrationMetadata: { - didIndyNamespace: 'pool1', - }, - didState: { - state: 'finished', - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - ], - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - verificationMethod: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', - type: 'Ed25519VerificationKey2018', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', - type: 'X25519KeyAgreementKey2019', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', - }, - ], - authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - }, - secret: { - privateKey, - }, - }, - }) - }) - - it('should correctly create a did:sov document with services', async () => { - const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('R1xKJw17sUoXhejEpugMYJ')) - - const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') - setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) - - const result = await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterVerificationMethod: 'did:sov:BzCbsNYhMrjHiqZDTUASHg#key-1', - role: 'STEWARD', - endpoints: { - endpoint: 'https://example.com/endpoint', - routingKeys: ['key-1'], - types: ['DIDComm', 'did-communication', 'endpoint'], - }, - }, - secret: { - privateKey, - }, - }) - - expect(registerPublicDidSpy).toHaveBeenCalledWith( - agentContext, - // Unqualified submitter did - 'BzCbsNYhMrjHiqZDTUASHg', - // submitter signing key, - expect.any(Key), - // Unqualified created indy did - 'R1xKJw17sUoXhejEpugMYJ', - // Verkey - expect.any(Key), - // Alias - 'Hello', - // Pool - { config: { indyNamespace: 'pool1' } }, - // Role - 'STEWARD' - ) - expect(JsonTransformer.toJSON(result)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didRegistrationMetadata: { - didIndyNamespace: 'pool1', - }, - didState: { - state: 'finished', - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - 'https://didcomm.org/messaging/contexts/v2', - ], - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - verificationMethod: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-1', - type: 'Ed25519VerificationKey2018', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1', - type: 'X25519KeyAgreementKey2019', - controller: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', - }, - ], - service: [ - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#endpoint', - serviceEndpoint: 'https://example.com/endpoint', - type: 'endpoint', - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#did-communication', - serviceEndpoint: 'https://example.com/endpoint', - type: 'did-communication', - priority: 0, - recipientKeys: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - routingKeys: ['key-1'], - accept: ['didcomm/aip2;env=rfc19'], - }, - { - id: 'did:sov:R1xKJw17sUoXhejEpugMYJ#didcomm-1', - serviceEndpoint: 'https://example.com/endpoint', - type: 'DIDComm', - routingKeys: ['key-1'], - accept: ['didcomm/v2'], - }, - ], - authentication: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - assertionMethod: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-1'], - keyAgreement: ['did:sov:R1xKJw17sUoXhejEpugMYJ#key-agreement-1'], - }, - secret: { - privateKey, - }, - }, - }) - }) - - it('should store the did document', async () => { - const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - - const registerPublicDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'registerPublicDid') - registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve('did')) - - const setEndpointsForDidSpy = jest.spyOn(indySdkSovDidRegistrar, 'setEndpointsForDid') - setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) - - const saveCalled = jest.fn() - eventEmitter.on>(RepositoryEventTypes.RecordSaved, saveCalled) - - await indySdkSovDidRegistrar.create(agentContext, { - method: 'sov', - options: { - alias: 'Hello', - submitterVerificationMethod: 'did:sov:BzCbsNYhMrjHiqZDTUASHg#key-1', - role: 'STEWARD', - endpoints: { - endpoint: 'https://example.com/endpoint', - routingKeys: ['key-1'], - types: ['DIDComm', 'did-communication', 'endpoint'], - }, - }, - secret: { - privateKey, - }, - }) - - expect(saveCalled).toHaveBeenCalledTimes(1) - const [saveEvent] = saveCalled.mock.calls[0] - - expect(saveEvent.payload.record).toMatchObject({ - did: 'did:sov:R1xKJw17sUoXhejEpugMYJ', - role: DidDocumentRole.Created, - _tags: { - recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], - qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', - }, - didDocument: undefined, - }) - }) - - it('should return an error state when calling update', async () => { - const result = await indySdkSovDidRegistrar.update() - - expect(result).toEqual({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: updating did:sov not implemented yet`, - }, - }) - }) - - it('should return an error state when calling deactivate', async () => { - const result = await indySdkSovDidRegistrar.deactivate() - - expect(result).toEqual({ - didDocumentMetadata: {}, - didRegistrationMetadata: {}, - didState: { - state: 'failed', - reason: `notImplemented: deactivating did:sov not implemented yet`, - }, - }) - }) -}) diff --git a/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json new file mode 100644 index 0000000000..c0bd51aa30 --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1R1xKJw17sUoXhejEpugMYJ.json @@ -0,0 +1,50 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1" + ], + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ", + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey", + "publicKeyBase58": "E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ", + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1", + "publicKeyBase58": "Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt" + } + ], + "authentication": ["did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#verkey"], + "keyAgreement": ["did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"], + "service": [ + { + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://ssi.com" + }, + { + "accept": ["didcomm/aip2;env=rfc19"], + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#did-communication", + "priority": 0, + "recipientKeys": ["did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#key-agreement-1"], + "routingKeys": [], + "serviceEndpoint": "https://ssi.com", + "type": "did-communication" + }, + { + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#profile", + "serviceEndpoint": "https://profile.com", + "type": "profile" + }, + { + "id": "did:indy:pool1:R1xKJw17sUoXhejEpugMYJ#hub", + "serviceEndpoint": "https://hub.com", + "type": "hub" + } + ] +} diff --git a/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json new file mode 100644 index 0000000000..a943f3bf9e --- /dev/null +++ b/packages/indy-sdk/src/dids/__tests__/__fixtures__/didIndyPool1WJz9mHyW9BZksioQnRsrAo.json @@ -0,0 +1,48 @@ +{ + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1", + "https://didcomm.org/messaging/contexts/v2" + ], + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo", + "verificationMethod": [ + { + "type": "Ed25519VerificationKey2018", + "controller": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo", + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#verkey", + "publicKeyBase58": "GyYtYWU1vjwd5PFJM4VSX5aUiSV3TyZMuLBJBTQvfdF8" + }, + { + "type": "X25519KeyAgreementKey2019", + "controller": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo", + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1", + "publicKeyBase58": "S3AQEEKkGYrrszT9D55ozVVX2XixYp8uynqVm4okbud" + } + ], + "authentication": ["did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#verkey"], + "keyAgreement": ["did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "service": [ + { + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#endpoint", + "type": "endpoint", + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#did-communication", + "type": "did-communication", + "priority": 0, + "recipientKeys": ["did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#key-agreement-1"], + "routingKeys": ["routingKey1", "routingKey2"], + "accept": ["didcomm/aip2;env=rfc19"], + "serviceEndpoint": "https://agent.com" + }, + { + "id": "did:indy:pool1:WJz9mHyW9BZksioQnRsrAo#didcomm-1", + "type": "DIDComm", + "serviceEndpoint": "https://agent.com", + "accept": ["didcomm/v2"], + "routingKeys": ["routingKey1", "routingKey2"] + } + ] +} diff --git a/packages/indy-sdk/src/dids/didIndyUtil.ts b/packages/indy-sdk/src/dids/didIndyUtil.ts new file mode 100644 index 0000000000..aee1812f32 --- /dev/null +++ b/packages/indy-sdk/src/dids/didIndyUtil.ts @@ -0,0 +1,68 @@ +import type { AgentContext } from '@aries-framework/core' + +import { + getKeyDidMappingByVerificationMethod, + AriesFrameworkError, + convertPublicKeyToX25519, + DidDocumentBuilder, + DidsApi, + TypedArrayEncoder, +} from '@aries-framework/core' + +import { DID_INDY_REGEX } from '../utils/did' + +export function parseIndyDid(did: string) { + const match = did.match(DID_INDY_REGEX) + if (match) { + const [, namespace, id] = match + return { namespace, id } + } else { + throw new AriesFrameworkError(`${did} is not a valid did:indy did`) + } +} + +// Create a base DIDDoc template according to https://hyperledger.github.io/indy-did-method/#base-diddoc-template +export function indyDidDocumentFromDid(did: string, publicKeyBase58: string) { + const verificationMethodId = `${did}#verkey` + + const builder = new DidDocumentBuilder(did) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') + .addVerificationMethod({ + controller: did, + id: verificationMethodId, + publicKeyBase58, + type: 'Ed25519VerificationKey2018', + }) + .addAuthentication(verificationMethodId) + + return builder +} + +export function createKeyAgreementKey(verkey: string) { + return TypedArrayEncoder.toBase58(convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(verkey))) +} + +/** + * Fetches the verification key for a given did:indy did and returns the key as a {@link Key} object. + * + * @throws {@link AriesFrameworkError} if the did could not be resolved or the key could not be extracted + */ +export async function verificationKeyForIndyDid(agentContext: AgentContext, did: string) { + // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did + // from the ledger to know which key is associated with the did + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const didResult = await didsApi.resolve(did) + + if (!didResult.didDocument) { + throw new AriesFrameworkError( + `Could not resolve did ${did}. ${didResult.didResolutionMetadata.error} ${didResult.didResolutionMetadata.message}` + ) + } + + // did:indy dids MUST have a verificationMethod with #verkey + const verificationMethod = didResult.didDocument.dereferenceKey(`${did}#verkey`) + const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) + const key = getKeyFromVerificationMethod(verificationMethod) + + return key +} diff --git a/packages/indy-sdk/src/dids/didSovUtil.ts b/packages/indy-sdk/src/dids/didSovUtil.ts index b5af6ee3f0..989e09f432 100644 --- a/packages/indy-sdk/src/dids/didSovUtil.ts +++ b/packages/indy-sdk/src/dids/didSovUtil.ts @@ -9,6 +9,8 @@ import { import { getFullVerkey } from '../utils/did' +export type DidCommServicesEndpointType = 'endpoint' | 'did-communication' | 'DIDComm' + export interface IndyEndpointAttrib { endpoint?: string types?: Array<'endpoint' | 'did-communication' | 'DIDComm'> @@ -16,6 +18,10 @@ export interface IndyEndpointAttrib { [key: string]: unknown } +/** + * Get a base did:sov did document based on the provided did and verkey + * https://sovrin-foundation.github.io/sovrin/spec/did-method-spec-template.html#crud-operation-definitions + */ export function sovDidDocumentFromDid(fullDid: string, verkey: string) { const verificationMethodId = `${fullDid}#key-1` const keyAgreementId = `${fullDid}#key-agreement-1` diff --git a/packages/indy-sdk/src/dids/index.ts b/packages/indy-sdk/src/dids/index.ts index 68eabe204d..8017bf8749 100644 --- a/packages/indy-sdk/src/dids/index.ts +++ b/packages/indy-sdk/src/dids/index.ts @@ -1,7 +1,3 @@ -export { - IndySdkSovDidRegistrar, - IndySdkSovDidCreateOptions, - IndySdkSovDidDeactivateOptions, - IndySdkSovDidUpdateOptions, -} from './IndySdkSovDidRegistrar' +export { IndySdkIndyDidRegistrar, IndySdkIndyDidCreateOptions } from './IndySdkIndyDidRegistrar' export { IndySdkSovDidResolver } from './IndySdkSovDidResolver' +export { IndySdkIndyDidResolver } from './IndySdkIndyDidResolver' diff --git a/packages/indy-sdk/src/index.ts b/packages/indy-sdk/src/index.ts index 64ed474b75..5857f00da2 100644 --- a/packages/indy-sdk/src/index.ts +++ b/packages/indy-sdk/src/index.ts @@ -1,11 +1,5 @@ // Dids -export { - IndySdkSovDidRegistrar, - IndySdkSovDidCreateOptions, - IndySdkSovDidDeactivateOptions, - IndySdkSovDidUpdateOptions, - IndySdkSovDidResolver, -} from './dids' +export * from './dids' // Ledger export { IndySdkPoolConfig } from './ledger' diff --git a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts index be9217b0ed..23928fafa5 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts @@ -17,7 +17,7 @@ import { Subject } from 'rxjs' import { IndySdkModuleConfig } from '../IndySdkModuleConfig' import { IndySdkError, isIndyError } from '../error' import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' -import { isSelfCertifiedDid } from '../utils/did' +import { DID_INDY_REGEX, isLegacySelfCertifiedDid } from '../utils/did' import { allSettled, onlyFulfilled, onlyRejected } from '../utils/promises' import { IndySdkPool } from './IndySdkPool' @@ -56,13 +56,39 @@ export class IndySdkPoolService { } /** - * Get the most appropriate pool for the given did. The algorithm is based on the approach as described in this document: + * Get the most appropriate pool for the given did. + * If the did is a qualified indy did, the pool will be determined based on the namespace. + * If it is a legacy unqualified indy did, the pool will be determined based on the algorithm as described in this document: * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit + * + * This method will optionally return a nym response when the did has been resolved to determine the ledger + * either now or in the past. The nymResponse can be used to prevent multiple ledger quries fetching the same + * did */ public async getPoolForDid( agentContext: AgentContext, did: string - ): Promise<{ pool: IndySdkPool; did: GetNymResponse }> { + ): Promise<{ pool: IndySdkPool; nymResponse?: GetNymResponse }> { + // Check if the did starts with did:indy + const match = did.match(DID_INDY_REGEX) + + if (match) { + const [, namespace] = match + + const pool = this.getPoolForNamespace(namespace) + + if (pool) return { pool } + + throw new IndySdkPoolError(`Pool for indy namespace '${namespace}' not found`) + } else { + return await this.getPoolForLegacyDid(agentContext, did) + } + } + + private async getPoolForLegacyDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndySdkPool; nymResponse: GetNymResponse }> { const pools = this.pools if (pools.length === 0) { @@ -78,7 +104,7 @@ export class IndySdkPoolService { // If we have the nym response with associated pool in the cache, we'll use that if (cachedNymResponse && pool) { this.logger.trace(`Found ledger '${pool.didIndyNamespace}' for did '${did}' in cache`) - return { did: cachedNymResponse.nymResponse, pool } + return { nymResponse: cachedNymResponse.nymResponse, pool } } const { successful, rejected } = await this.getSettledDidResponsesFromPools(did, pools) @@ -103,7 +129,7 @@ export class IndySdkPoolService { // We take the first self certifying DID as we take the order in the // indyLedgers config as the order of preference of ledgers let value = successful.find((response) => - isSelfCertifiedDid(response.value.did.did, response.value.did.verkey) + isLegacySelfCertifiedDid(response.value.did.did, response.value.did.verkey) )?.value if (!value) { @@ -123,7 +149,8 @@ export class IndySdkPoolService { nymResponse: value.did, indyNamespace: value.pool.didIndyNamespace, } satisfies CachedDidResponse) - return { pool: value.pool, did: value.did } + + return { pool: value.pool, nymResponse: value.did } } private async getSettledDidResponsesFromPools(did: string, pools: IndySdkPool[]) { diff --git a/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts index ee595c31ec..20d79e0564 100644 --- a/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts +++ b/packages/indy-sdk/src/ledger/__tests__/IndySdkPoolService.test.ts @@ -115,6 +115,12 @@ describe('IndySdkPoolService', () => { expect(poolService.getPoolForDid(agentContext, did)).rejects.toThrowError(IndySdkPoolNotFoundError) }) + it('should return the pool based on namespace if did is a valid did:indy did', async () => { + const { pool } = await poolService.getPoolForDid(agentContext, 'did:indy:sovrin:Y5bj4SjCiTM9PgeheKAiXx') + + expect(pool.didIndyNamespace).toBe('sovrin') + }) + it('should return the pool if the did was only found on one ledger', async () => { const did = 'TL1EaPFCZ8Si5aUrqScBDt' // Only found on one ledger diff --git a/packages/indy-sdk/src/utils/__tests__/did.test.ts b/packages/indy-sdk/src/utils/__tests__/did.test.ts index 45344136d9..222f9898fd 100644 --- a/packages/indy-sdk/src/utils/__tests__/did.test.ts +++ b/packages/indy-sdk/src/utils/__tests__/did.test.ts @@ -1,4 +1,4 @@ -import { isAbbreviatedVerkey, isFullVerkey, isSelfCertifiedDid } from '../did' +import { isAbbreviatedVerkey, isFullVerkey, isLegacySelfCertifiedDid } from '../did' const validAbbreviatedVerkeys = [ '~PKAYz8Ev4yoQgr2LaMAWFx', @@ -36,15 +36,19 @@ const invalidFullVerkeys = [ describe('Utils | Did', () => { describe('isSelfCertifiedDid()', () => { test('returns true if the verkey is abbreviated', () => { - expect(isSelfCertifiedDid('PW8ZHpNupeWXbmpPWog6Ki', '~QQ5jiH1dgXPAnvHdJvazn9')).toBe(true) + expect(isLegacySelfCertifiedDid('PW8ZHpNupeWXbmpPWog6Ki', '~QQ5jiH1dgXPAnvHdJvazn9')).toBe(true) }) test('returns true if the verkey is not abbreviated and the did is generated from the verkey', () => { - expect(isSelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'HyEoPRNvC7q4jj5joUo8AWYtxbNccbEnTAeuMYkpmNS2')).toBe(true) + expect(isLegacySelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'HyEoPRNvC7q4jj5joUo8AWYtxbNccbEnTAeuMYkpmNS2')).toBe( + true + ) }) test('returns false if the verkey is not abbreviated and the did is not generated from the verkey', () => { - expect(isSelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'AcU7DnRqoXGYATD6VqsRq4eHuz55gdM3uzFBEhFd6rGh')).toBe(false) + expect(isLegacySelfCertifiedDid('Y8q4Aq6gRAcmB6jjKk3Z7t', 'AcU7DnRqoXGYATD6VqsRq4eHuz55gdM3uzFBEhFd6rGh')).toBe( + false + ) }) }) diff --git a/packages/indy-sdk/src/utils/did.ts b/packages/indy-sdk/src/utils/did.ts index 90b465a0f6..7d78cd09e2 100644 --- a/packages/indy-sdk/src/utils/did.ts +++ b/packages/indy-sdk/src/utils/did.ts @@ -19,6 +19,7 @@ import { Buffer, TypedArrayEncoder } from '@aries-framework/core' export const FULL_VERKEY_REGEX = /^[1-9A-HJ-NP-Za-km-z]{43,44}$/ export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ +export const DID_INDY_REGEX = /^did:indy:((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?):([1-9A-HJ-NP-Za-km-z]{21,22})$/ /** * Check whether the did is a self certifying did. If the verkey is abbreviated this method @@ -27,14 +28,14 @@ export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ * * @return Boolean indicating whether the did is self certifying */ -export function isSelfCertifiedDid(did: string, verkey: string): boolean { +export function isLegacySelfCertifiedDid(did: string, verkey: string): boolean { // If the verkey is Abbreviated, it means the full verkey // is the did + the verkey if (isAbbreviatedVerkey(verkey)) { return true } - const didFromVerkey = indyDidFromPublicKeyBase58(verkey) + const didFromVerkey = legacyIndyDidFromPublicKeyBase58(verkey) if (didFromVerkey === did) { return true @@ -43,7 +44,7 @@ export function isSelfCertifiedDid(did: string, verkey: string): boolean { return false } -export function indyDidFromPublicKeyBase58(publicKeyBase58: string): string { +export function legacyIndyDidFromPublicKeyBase58(publicKeyBase58: string): string { const buffer = TypedArrayEncoder.fromBase58(publicKeyBase58) const did = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) diff --git a/packages/indy-sdk/tests/__fixtures__/anoncreds.ts b/packages/indy-sdk/tests/__fixtures__/anoncreds.ts new file mode 100644 index 0000000000..eb978ec748 --- /dev/null +++ b/packages/indy-sdk/tests/__fixtures__/anoncreds.ts @@ -0,0 +1,30 @@ +export const credentialDefinitionValue = { + primary: { + n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', + s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', + r: { + master_secret: + '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', + name: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', + }, + rctxt: + '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', + z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', + }, + revocation: { + g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + g_dash: + '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + htilde: + '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h_cap: + '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + }, +} diff --git a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts similarity index 68% rename from packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts rename to packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts index 4518010326..c007eaa561 100644 --- a/packages/indy-sdk/tests/sov-did-registrar.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts @@ -1,14 +1,14 @@ -import type { IndySdkSovDidCreateOptions } from '../src/dids/IndySdkSovDidRegistrar' +import type { IndySdkIndyDidCreateOptions } from '../src' import { Agent, TypedArrayEncoder, convertPublicKeyToX25519, JsonTransformer } from '@aries-framework/core' import { generateKeyPairFromSeed } from '@stablelib/ed25519' import { getAgentOptions, importExistingIndyDidFromPrivateKey, publicDidSeed } from '../../core/tests' -import { indyDidFromPublicKeyBase58 } from '../src/utils/did' +import { legacyIndyDidFromPublicKeyBase58 } from '../src/utils/did' import { getIndySdkModules } from './setupIndySdkModule' -const agentOptions = getAgentOptions('Faber Dids Registrar', {}, getIndySdkModules()) +const agentOptions = getAgentOptions('Indy Sdk Indy Did Registrar', {}, getIndySdkModules()) describe('dids', () => { let agent: Agent> @@ -23,7 +23,7 @@ describe('dids', () => { await agent.wallet.delete() }) - it('should create a did:sov did', async () => { + it('should create a did:indy did', async () => { // Add existing endorser did to the wallet const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( agent, @@ -41,12 +41,12 @@ describe('dids', () => { const publicKeyEd25519 = generateKeyPairFromSeed(privateKey).publicKey const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(publicKeyEd25519)) const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(publicKeyEd25519) - const indyDid = indyDidFromPublicKeyBase58(ed25519PublicKeyBase58) + const unqualifiedDid = legacyIndyDidFromPublicKeyBase58(ed25519PublicKeyBase58) - const did = await agent.dids.create({ - method: 'sov', + const did = await agent.dids.create({ + method: 'indy', options: { - submitterVerificationMethod: `did:sov:${unqualifiedSubmitterDid}#key-1`, + submitterDid: `did:indy:pool:localtest:${unqualifiedSubmitterDid}`, alias: 'Alias', endpoints: { endpoint: 'https://example.com/endpoint', @@ -60,15 +60,11 @@ describe('dids', () => { }) expect(JsonTransformer.toJSON(did)).toMatchObject({ - didDocumentMetadata: { - qualifiedIndyDid: `did:indy:pool:localtest:${indyDid}`, - }, - didRegistrationMetadata: { - didIndyNamespace: 'pool:localtest', - }, + didDocumentMetadata: {}, + didRegistrationMetadata: {}, didState: { state: 'finished', - did: `did:sov:${indyDid}`, + did: `did:indy:pool:localtest:${unqualifiedDid}`, didDocument: { '@context': [ 'https://w3id.org/did/v1', @@ -80,47 +76,47 @@ describe('dids', () => { controller: undefined, verificationMethod: [ { - id: `did:sov:${indyDid}#key-1`, + id: `did:indy:pool:localtest:${unqualifiedDid}#verkey`, type: 'Ed25519VerificationKey2018', - controller: `did:sov:${indyDid}`, + controller: `did:indy:pool:localtest:${unqualifiedDid}`, publicKeyBase58: ed25519PublicKeyBase58, }, { - id: `did:sov:${indyDid}#key-agreement-1`, + id: `did:indy:pool:localtest:${unqualifiedDid}#key-agreement-1`, type: 'X25519KeyAgreementKey2019', - controller: `did:sov:${indyDid}`, + controller: `did:indy:pool:localtest:${unqualifiedDid}`, publicKeyBase58: x25519PublicKeyBase58, }, ], service: [ { - id: `did:sov:${indyDid}#endpoint`, + id: `did:indy:pool:localtest:${unqualifiedDid}#endpoint`, serviceEndpoint: 'https://example.com/endpoint', type: 'endpoint', }, { accept: ['didcomm/aip2;env=rfc19'], - id: `did:sov:${indyDid}#did-communication`, + id: `did:indy:pool:localtest:${unqualifiedDid}#did-communication`, priority: 0, - recipientKeys: [`did:sov:${indyDid}#key-agreement-1`], + recipientKeys: [`did:indy:pool:localtest:${unqualifiedDid}#key-agreement-1`], routingKeys: ['a-routing-key'], serviceEndpoint: 'https://example.com/endpoint', type: 'did-communication', }, { accept: ['didcomm/v2'], - id: `did:sov:${indyDid}#didcomm-1`, + id: `did:indy:pool:localtest:${unqualifiedDid}#didcomm-1`, routingKeys: ['a-routing-key'], serviceEndpoint: 'https://example.com/endpoint', type: 'DIDComm', }, ], - authentication: [`did:sov:${indyDid}#key-1`], - assertionMethod: [`did:sov:${indyDid}#key-1`], - keyAgreement: [`did:sov:${indyDid}#key-agreement-1`], + authentication: [`did:indy:pool:localtest:${unqualifiedDid}#verkey`], + assertionMethod: undefined, + keyAgreement: [`did:indy:pool:localtest:${unqualifiedDid}#key-agreement-1`], capabilityInvocation: undefined, capabilityDelegation: undefined, - id: `did:sov:${indyDid}`, + id: `did:indy:pool:localtest:${unqualifiedDid}`, }, secret: { privateKey, diff --git a/packages/indy-sdk/tests/indy-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/indy-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..839db5e4df --- /dev/null +++ b/packages/indy-sdk/tests/indy-did-resolver.e2e.test.ts @@ -0,0 +1,99 @@ +import type { IndySdkIndyDidCreateOptions } from '../src' + +import { Agent, AriesFrameworkError, JsonTransformer, TypedArrayEncoder } from '@aries-framework/core' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey, publicDidSeed } from '../../core/tests/helpers' + +import { getIndySdkModules } from './setupIndySdkModule' + +const agent = new Agent(getAgentOptions('Indy SDK Indy DID resolver', {}, getIndySdkModules())) + +describe('Indy SDK Indy DID resolver', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + it('should resolve a did:indy did', async () => { + // Add existing endorser did to the wallet + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString(publicDidSeed) + ) + + const createResult = await agent.dids.create({ + method: 'indy', + options: { + submitterDid: `did:indy:pool:localtest:${unqualifiedSubmitterDid}`, + alias: 'Alias', + role: 'TRUSTEE', + endpoints: { + endpoint: 'http://localhost:3000', + }, + }, + }) + + // Terrible, but the did can't be immediately resolved, so we need to wait a bit + await new Promise((res) => setTimeout(res, 1000)) + + if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') + + const didResult = await agent.dids.resolve(createResult.didState.did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: createResult.didState.did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: createResult.didState.did, + id: `${createResult.didState.did}#verkey`, + publicKeyBase58: expect.any(String), + }, + { + controller: createResult.didState.did, + type: 'X25519KeyAgreementKey2019', + id: `${createResult.didState.did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${createResult.didState.did}#verkey`], + assertionMethod: undefined, + keyAgreement: [`${createResult.didState.did}#key-agreement-1`], + service: [ + { + id: `${createResult.didState.did}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${createResult.didState.did}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${createResult.didState.did}#key-agreement-1`], + routingKeys: [], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts index 046767b8cc..c2401e41a3 100644 --- a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts @@ -7,8 +7,11 @@ import { publicDidSeed, } from '../../core/tests/helpers' import { IndySdkAnonCredsRegistry } from '../src/anoncreds/services/IndySdkAnonCredsRegistry' +import { IndySdkPoolService } from '../src/ledger' +import { assertIndySdkWallet } from '../src/utils/assertIndySdkWallet' -import { getIndySdkModules } from './setupIndySdkModule' +import { credentialDefinitionValue } from './__fixtures__/anoncreds' +import { getIndySdkModules, indySdk } from './setupIndySdkModule' const agentConfig = getAgentConfig('IndySdkAnonCredsRegistry') const indySdkModules = getIndySdkModules() @@ -20,6 +23,8 @@ const agent = new Agent({ }) const indySdkAnonCredsRegistry = new IndySdkAnonCredsRegistry() +const indySdkPoolService = agent.dependencyManager.resolve(IndySdkPoolService) +const pool = indySdkPoolService.getPoolForNamespace('pool:localtest') describe('IndySdkAnonCredsRegistry', () => { beforeAll(async () => { @@ -34,56 +39,58 @@ describe('IndySdkAnonCredsRegistry', () => { await agent.wallet.delete() }) + // TODO: use different issuer for schema and credential definition to catch possible bugs // One test as the credential definition depends on the schema test('register and resolve a schema and credential definition', async () => { const dynamicVersion = `1.${Math.random() * 100}` + const legacyIssuerId = 'TL1EaPFCZ8Si5aUrqScBDt' + const didIndyIssuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + + const legacySchemaId = `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}` + const didIndySchemaId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/SCHEMA/test/${dynamicVersion}` + const schemaResult = await indySdkAnonCredsRegistry.registerSchema(agent.context, { schema: { attrNames: ['name'], - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', - name: 'test - 11', + issuerId: didIndyIssuerId, + name: 'test', version: dynamicVersion, }, - options: { - didIndyNamespace: 'pool:localtest', - }, + options: {}, }) + console.log(schemaResult) expect(schemaResult).toMatchObject({ schemaState: { state: 'finished', schema: { attrNames: ['name'], - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', - name: 'test - 11', + issuerId: didIndyIssuerId, + name: 'test', version: dynamicVersion, }, - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test - 11:${dynamicVersion}`, + schemaId: didIndySchemaId, }, registrationMetadata: {}, schemaMetadata: { indyLedgerSeqNo: expect.any(Number), - didIndyNamespace: 'pool:localtest', }, }) // Wait some time before resolving credential definition object await new Promise((res) => setTimeout(res, 1000)) - const schemaResponse = await indySdkAnonCredsRegistry.getSchema( - agent.context, - `TL1EaPFCZ8Si5aUrqScBDt:2:test - 11:${dynamicVersion}` - ) - - expect(schemaResponse).toMatchObject({ + // Resolve using legacy schema id + const legacySchema = await indySdkAnonCredsRegistry.getSchema(agent.context, legacySchemaId) + expect(legacySchema).toMatchObject({ schema: { attrNames: ['name'], - name: 'test - 11', + name: 'test', version: dynamicVersion, issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', }, - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test - 11:${dynamicVersion}`, + schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, resolutionMetadata: {}, schemaMetadata: { didIndyNamespace: 'pool:localtest', @@ -91,58 +98,47 @@ describe('IndySdkAnonCredsRegistry', () => { }, }) + // Resolve using did indy schema id + const didIndySchema = await indySdkAnonCredsRegistry.getSchema(agent.context, didIndySchemaId) + expect(didIndySchema).toMatchObject({ + schema: { + attrNames: ['name'], + name: 'test', + version: dynamicVersion, + issuerId: didIndyIssuerId, + }, + schemaId: didIndySchemaId, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: 'pool:localtest', + indyLedgerSeqNo: expect.any(Number), + }, + }) + + const legacyCredentialDefinitionId = `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG` + const didIndyCredentialDefinitionId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG` const credentialDefinitionResult = await indySdkAnonCredsRegistry.registerCredentialDefinition(agent.context, { credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, tag: 'TAG', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test - 11:${dynamicVersion}`, + schemaId: didIndySchemaId, type: 'CL', - value: { - primary: { - n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', - s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', - r: { - age: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', - master_secret: - '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', - }, - rctxt: - '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', - z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', - }, - }, - }, - options: { - didIndyNamespace: 'pool:localtest', + value: credentialDefinitionValue, }, + options: {}, }) expect(credentialDefinitionResult).toMatchObject({ - credentialDefinitionMetadata: { - didIndyNamespace: 'pool:localtest', - }, + credentialDefinitionMetadata: {}, credentialDefinitionState: { credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, tag: 'TAG', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test - 11:${dynamicVersion}`, + schemaId: didIndySchemaId, type: 'CL', - value: { - primary: { - n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', - s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', - r: { - age: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', - master_secret: - '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', - }, - rctxt: - '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', - z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', - }, - }, + value: {}, }, - credentialDefinitionId: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG`, + credentialDefinitionId: didIndyCredentialDefinitionId, state: 'finished', }, registrationMetadata: {}, @@ -151,37 +147,202 @@ describe('IndySdkAnonCredsRegistry', () => { // Wait some time before resolving credential definition object await new Promise((res) => setTimeout(res, 1000)) - const credentialDefinitionResponse = await indySdkAnonCredsRegistry.getCredentialDefinition( + // Resolve using legacy credential definition id + const legacyCredentialDefinition = await indySdkAnonCredsRegistry.getCredentialDefinition( agent.context, - credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId as string + legacyCredentialDefinitionId + ) + expect(legacyCredentialDefinition).toMatchObject({ + credentialDefinitionId: legacyCredentialDefinitionId, + credentialDefinition: { + issuerId: legacyIssuerId, + schemaId: legacySchemaId, + tag: 'TAG', + type: 'CL', + value: credentialDefinitionValue, + }, + credentialDefinitionMetadata: { + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + // resolve using did indy credential definition id + const didIndyCredentialDefinition = await indySdkAnonCredsRegistry.getCredentialDefinition( + agent.context, + didIndyCredentialDefinitionId ) - expect(credentialDefinitionResponse).toMatchObject({ - credentialDefinitionId: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG`, + expect(didIndyCredentialDefinition).toMatchObject({ + credentialDefinitionId: didIndyCredentialDefinitionId, credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test - 11:${dynamicVersion}`, + issuerId: didIndyIssuerId, + schemaId: didIndySchemaId, tag: 'TAG', type: 'CL', + value: credentialDefinitionValue, + }, + credentialDefinitionMetadata: { + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + assertIndySdkWallet(agent.context.wallet) + + // We don't support creating a revocation registry using AFJ yet, so we directly use indy-sdk to register the revocation registry + const legacyRevocationRegistryId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` + const didIndyRevocationRegistryId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` + const revocationRegistryRequest = await indySdk.buildRevocRegDefRequest('TL1EaPFCZ8Si5aUrqScBDt', { + id: legacyRevocationRegistryId, + credDefId: legacyCredentialDefinitionId, + revocDefType: 'CL_ACCUM', + tag: 'tag', + value: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + maxCredNum: 100, + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + }, + ver: '1.0', + }) + + await indySdkPoolService.submitWriteRequest(agent.context, pool, revocationRegistryRequest, legacyIssuerId) + + const legacyRevocationRegistryDefinition = await indySdkAnonCredsRegistry.getRevocationRegistryDefinition( + agent.context, + legacyRevocationRegistryId + ) + expect(legacyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinition: { + issuerId: legacyIssuerId, + revocDefType: 'CL_ACCUM', value: { - primary: { - n: '92511867718854414868106363741369833735017762038454769060600859608405811709675033445666654908195955460485998711087020152978597220168927505650092431295783175164390266561239892662085428655566792056852960599485298025843840058914610127716620252006466964070280255168745873592143068949458568751438337748294055976926080232538440619420568859737673474560851456027625679328271511966332808025880807996449998057729417608399774744254122385012832309402226532031122728445959276178939234308090390331654445053482963947804769291501664200141562885660084823885847247231002821472258218384342423605116504024514572826071246440130942849549441', - r: { - age: '676933340341980399002624386891134393471002096508227567343731826159610079436978196421307099268754545293545727546242372579987825752872485684085629459107300175443328323289748793060894500514926703654606851666031895448970879827423190730510730624784665299646624113512701254199984520803796529034094958026048762178753193812250643294518237843809104055653333871102658177900702978008644780459400512716361564897282969982554031820285585105004870317861287847206222714589633178648982299799311192432563797220854755882933052881306804544233529886513105815543097685128456041780804442879272476590077760678785460726492895806240870944398', - master_secret: - '57770757113548032970308439965749734133430520933173186296299026579579930337912607419798836831937319372744879560676750427054135869214212225572618340088847222727882935159356459822445182287686057012197046378986248048722180093079919306125315662058290895629438767985427829790980355162853804522854494960613869765167538645624719923127052541372069255024631093663068055100579264049925388231368871107383977060590248865498902704546409806115171120555709438784189721957301548212242748685629860268468247494986146122636455769804467583612610341632602695197189514316033637331733820369170763954604394734655429769801516997967996980978751', + maxCredNum: 100, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', }, - rctxt: - '19574881057684356733946284215946569464410211018678168661028327420122678446653210056362495902735819742274128834330867933095119512313591151219353395069123546495720010325822330866859140765940839241212947354612836044244554152389691282543839111284006009168728161183863936810142428875817934316327118674532328892591410224676539770085459540786747902789677759379901079898127879301595929571621032704093287675668250862222728331030586585586110859977896767318814398026750215625180255041545607499673023585546720788973882263863911222208020438685873501025545464213035270207099419236974668665979962146355749687924650853489277747454993', - s: '80388543865249952799447792504739237616187770512259677275061283897050980768551818104137338144380636412773836688624071360386172349725818126495487584981520630638409717065318132420766896092370913800616033623618952639023946750307405126873476182540669638841562357523429245685476919178722373320218824590869735129801004394337640642997250464303104754942997839179333543643110326022824394934965538190976474473353762308333205671176627192797138375084260446324344637548455228161138089974447059481109651156379803576163576511072261388342837813901850712083922506433336723723235701670225584863772222447543742649328218950436824219992164', - z: '18569464356833363098514177097771727133940629758890641648661259687745137028161881113251218061243607037717553708179509640909238773964066423807945164288256211132195919975343578956381001087353353060599758005375631247614777454313440511375923345538396573548499287265163879524050255226779884271432737062283353279122281220812931572456820130441114446870167673796490210349453498315913599982158253821945225264065364670730546176140788405935081171854642125236557475395879246419105888077042924382595999612137336915304205628167917473420377397118829734604949103124514367857266518654728464539418834291071874052392799652266418817991437', }, }, + tag: 'tag', + credDefId: legacyCredentialDefinitionId, }, - credentialDefinitionMetadata: { + revocationRegistryDefinitionMetadata: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + const didIndyRevocationRegistryDefinition = await indySdkAnonCredsRegistry.getRevocationRegistryDefinition( + agent.context, + didIndyRevocationRegistryId + ) + expect(didIndyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: didIndyRevocationRegistryId, + revocationRegistryDefinition: { + issuerId: didIndyIssuerId, + revocDefType: 'CL_ACCUM', + value: { + maxCredNum: 100, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + }, + tag: 'tag', + credDefId: didIndyCredentialDefinitionId, + }, + revocationRegistryDefinitionMetadata: { + issuanceType: 'ISSUANCE_BY_DEFAULT', didIndyNamespace: 'pool:localtest', }, resolutionMetadata: {}, }) + + // indySdk.buildRevRegEntry panics, so we just pass a custom request directly + const entryResponse = await indySdkPoolService.submitWriteRequest( + agent.context, + pool, + { + identifier: legacyIssuerId, + operation: { + revocDefType: 'CL_ACCUM', + revocRegDefId: legacyRevocationRegistryId, + type: '114', + value: { + accum: + '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', + }, + }, + protocolVersion: 2, + reqId: Math.floor(Math.random() * 1000000), + }, + legacyIssuerId + ) + + const legacyRevocationStatusList = await indySdkAnonCredsRegistry.getRevocationStatusList( + agent.context, + legacyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime + ) + + expect(legacyRevocationStatusList).toMatchObject({ + resolutionMetadata: {}, + revocationStatusList: { + issuerId: legacyIssuerId, + currentAccumulator: + '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', + revRegId: legacyRevocationRegistryId, + revocationList: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', + }, + }) + + const didIndyRevocationStatusList = await indySdkAnonCredsRegistry.getRevocationStatusList( + agent.context, + didIndyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime + ) + + expect(didIndyRevocationStatusList).toMatchObject({ + resolutionMetadata: {}, + revocationStatusList: { + issuerId: didIndyIssuerId, + currentAccumulator: + '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', + revRegId: didIndyRevocationRegistryId, + revocationList: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', + }, + }) }) }) diff --git a/packages/indy-sdk/tests/setupIndySdkModule.ts b/packages/indy-sdk/tests/setupIndySdkModule.ts index c0e8ee1313..b4a30f799a 100644 --- a/packages/indy-sdk/tests/setupIndySdkModule.ts +++ b/packages/indy-sdk/tests/setupIndySdkModule.ts @@ -2,7 +2,13 @@ import { DidsModule, KeyDidRegistrar, KeyDidResolver, utils } from '@aries-frame import indySdk from 'indy-sdk' import { genesisPath, taaVersion, taaAcceptanceMechanism } from '../../core/tests/helpers' -import { IndySdkModule, IndySdkModuleConfig, IndySdkSovDidRegistrar, IndySdkSovDidResolver } from '../src' +import { + IndySdkModule, + IndySdkModuleConfig, + IndySdkIndyDidRegistrar, + IndySdkSovDidResolver, + IndySdkIndyDidResolver, +} from '../src' export { indySdk } @@ -23,7 +29,7 @@ export const getIndySdkModuleConfig = () => export const getIndySdkModules = () => ({ indySdk: new IndySdkModule(getIndySdkModuleConfig()), dids: new DidsModule({ - registrars: [new IndySdkSovDidRegistrar(), new KeyDidRegistrar()], - resolvers: [new IndySdkSovDidResolver(), new KeyDidResolver()], + registrars: [new IndySdkIndyDidRegistrar(), new KeyDidRegistrar()], + resolvers: [new IndySdkSovDidResolver(), new IndySdkIndyDidResolver(), new KeyDidResolver()], }), }) diff --git a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts index b1847a3f6d..ef220a59a3 100644 --- a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -1,8 +1,9 @@ -import type { IndySdkSovDidCreateOptions } from '../src/dids/IndySdkSovDidRegistrar' +import type { IndySdkIndyDidCreateOptions } from '../src' import { Agent, AriesFrameworkError, JsonTransformer, TypedArrayEncoder } from '@aries-framework/core' import { getAgentOptions, importExistingIndyDidFromPrivateKey, publicDidSeed } from '../../core/tests/helpers' +import { parseIndyDid } from '../src/dids/didIndyUtil' import { getIndySdkModules } from './setupIndySdkModule' @@ -25,12 +26,15 @@ describe('Indy SDK Sov DID resolver', () => { TypedArrayEncoder.fromString(publicDidSeed) ) - const createResult = await agent.dids.create({ - method: 'sov', + const createResult = await agent.dids.create({ + method: 'indy', options: { - submitterVerificationMethod: `did:sov:${unqualifiedSubmitterDid}#key-1`, + submitterDid: `did:indy:pool:localtest:${unqualifiedSubmitterDid}`, alias: 'Alias', role: 'TRUSTEE', + endpoints: { + endpoint: 'http://localhost:3000', + }, }, }) @@ -38,7 +42,10 @@ describe('Indy SDK Sov DID resolver', () => { await new Promise((res) => setTimeout(res, 1000)) if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') - const didResult = await agent.dids.resolve(createResult.didState.did) + + const { id: unqualifiedDid } = parseIndyDid(createResult.didState.did) + const sovDid = `did:sov:${unqualifiedDid}` + const didResult = await agent.dids.resolve(sovDid) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ didDocument: { @@ -47,29 +54,44 @@ describe('Indy SDK Sov DID resolver', () => { 'https://w3id.org/security/suites/ed25519-2018/v1', 'https://w3id.org/security/suites/x25519-2019/v1', ], - id: createResult.didState.did, + id: sovDid, alsoKnownAs: undefined, controller: undefined, verificationMethod: [ { type: 'Ed25519VerificationKey2018', - controller: createResult.didState.did, - id: `${createResult.didState.did}#key-1`, + controller: sovDid, + id: `${sovDid}#key-1`, publicKeyBase58: expect.any(String), }, { - controller: createResult.didState.did, + controller: sovDid, type: 'X25519KeyAgreementKey2019', - id: `${createResult.didState.did}#key-agreement-1`, + id: `${sovDid}#key-agreement-1`, publicKeyBase58: expect.any(String), }, ], capabilityDelegation: undefined, capabilityInvocation: undefined, - authentication: [`${createResult.didState.did}#key-1`], - assertionMethod: [`${createResult.didState.did}#key-1`], - keyAgreement: [`${createResult.didState.did}#key-agreement-1`], - service: undefined, + authentication: [`${sovDid}#key-1`], + assertionMethod: [`${sovDid}#key-1`], + keyAgreement: [`${sovDid}#key-agreement-1`], + service: [ + { + id: `${sovDid}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${sovDid}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${sovDid}#key-agreement-1`], + routingKeys: [], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + ], }, didDocumentMetadata: {}, didResolutionMetadata: { From 333b185573becee7565f49357298963cef2fb82b Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Sat, 25 Feb 2023 17:45:44 +0100 Subject: [PATCH 02/11] fix: broken tests Signed-off-by: Timo Glastra --- .../__tests__/AnonCredsRsServices.test.ts | 2 +- packages/anoncreds-rs/tests/indy-flow.test.ts | 2 +- .../legacy-indy-format-services.test.ts | 35 ++++++---- .../tests/InMemoryAnonCredsRegistry.ts | 66 +++++++++++++++---- packages/anoncreds/tests/anoncreds.test.ts | 32 ++++----- .../anoncreds/tests/legacyAnonCredsSetup.ts | 40 +++++++---- .../services/IndySdkHolderService.ts | 6 +- .../services/IndySdkIssuerService.ts | 19 ++++-- .../services/IndySdkVerifierService.ts | 6 +- .../utils/__tests__/identifiers.test.ts | 4 +- .../utils/__tests__/transform.test.ts | 20 +++--- .../src/anoncreds/utils/identifiers.ts | 5 +- .../__tests__/IndySdkSovDidResolver.test.ts | 4 ++ .../indy-sdk-anoncreds-registry.e2e.test.ts | 8 +-- 14 files changed, 172 insertions(+), 77 deletions(-) diff --git a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts index 019063bcbb..e0ba7089a9 100644 --- a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts +++ b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts @@ -48,7 +48,7 @@ const agentContext = getAgentContext({ describe('AnonCredsRsServices', () => { test('issuance flow without revocation', async () => { - const issuerId = 'issuer:uri' + const issuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' const schema = await anonCredsIssuerService.createSchema(agentContext, { attrNames: ['name', 'age'], diff --git a/packages/anoncreds-rs/tests/indy-flow.test.ts b/packages/anoncreds-rs/tests/indy-flow.test.ts index d3b53ed6fe..eadaffd740 100644 --- a/packages/anoncreds-rs/tests/indy-flow.test.ts +++ b/packages/anoncreds-rs/tests/indy-flow.test.ts @@ -69,7 +69,7 @@ const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService( const legacyIndyProofFormatService = new LegacyIndyProofFormatService() // This is just so we don't have to register an actually indy did (as we don't have the indy did registrar configured) -const indyDid = 'TL1EaPFCZ8Si5aUrqScBDt' +const indyDid = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' describe('Legacy indy format services using anoncreds-rs', () => { test('issuance and verification flow starting from proposal without negotiation and without revocation', async () => { diff --git a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts index 2a8e628097..4606899650 100644 --- a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts +++ b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts @@ -22,7 +22,13 @@ import { IndySdkWallet, } from '../../../../indy-sdk/src' import { IndySdkRevocationService } from '../../../../indy-sdk/src/anoncreds/services/IndySdkRevocationService' -import { indyDidFromPublicKeyBase58 } from '../../../../indy-sdk/src/utils/did' +import { + getLegacyCredentialDefinitionId, + getLegacySchemaId, + parseCredentialDefinitionId, + parseSchemaId, +} from '../../../../indy-sdk/src/anoncreds/utils/identifiers' +import { legacyIndyDidFromPublicKeyBase58 } from '../../../../indy-sdk/src/utils/did' import { InMemoryAnonCredsRegistry } from '../../../tests/InMemoryAnonCredsRegistry' import { AnonCredsModuleConfig } from '../../AnonCredsModuleConfig' import { AnonCredsLinkSecretRecord, AnonCredsLinkSecretRepository } from '../../repository' @@ -79,7 +85,8 @@ describe('Legacy indy format services', () => { test('issuance and verification flow starting from proposal without negotiation and without revocation', async () => { // This is just so we don't have to register an actual indy did (as we don't have the indy did registrar configured) const key = await wallet.createKey({ keyType: KeyType.Ed25519 }) - const indyDid = indyDidFromPublicKeyBase58(key.publicKeyBase58) + const unqualifiedIndyDid = legacyIndyDidFromPublicKeyBase58(key.publicKeyBase58) + const indyDid = `did:indy:pool1:${unqualifiedIndyDid}` // Create link secret await anonCredsHolderService.createLinkSecret(agentContext, { @@ -155,6 +162,12 @@ describe('Legacy indy format services', () => { }), ] + const cd = parseCredentialDefinitionId(credentialDefinitionState.credentialDefinitionId) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.didIdentifier, cd.schemaSeqNo, cd.tag) + + const s = parseSchemaId(schemaState.schemaId) + const legacySchemaId = getLegacySchemaId(s.didIdentifier, s.schemaName, s.schemaVersion) + // Holder creates proposal holderCredentialRecord.credentialAttributes = credentialAttributes const { attachment: proposalAttachment } = await indyCredentialFormatService.createProposal(agentContext, { @@ -162,7 +175,7 @@ describe('Legacy indy format services', () => { credentialFormats: { indy: { attributes: credentialAttributes, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + credentialDefinitionId: legacyCredentialDefinitionId, }, }, }) @@ -225,16 +238,16 @@ describe('Legacy indy format services', () => { age: '25', name: 'John', }, - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + schemaId: legacySchemaId, + credentialDefinitionId: legacyCredentialDefinitionId, revocationRegistryId: null, credentialRevocationId: null, }) expect(holderCredentialRecord.metadata.data).toEqual({ '_anonCreds/anonCredsCredential': { - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + schemaId: legacySchemaId, + credentialDefinitionId: legacyCredentialDefinitionId, }, '_anonCreds/anonCredsCredentialRequest': { master_secret_blinding_data: expect.any(Object), @@ -245,8 +258,8 @@ describe('Legacy indy format services', () => { expect(issuerCredentialRecord.metadata.data).toEqual({ '_anonCreds/anonCredsCredential': { - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + schemaId: legacySchemaId, + credentialDefinitionId: legacyCredentialDefinitionId, }, }) @@ -267,14 +280,14 @@ describe('Legacy indy format services', () => { attributes: [ { name: 'name', - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + credentialDefinitionId: legacyCredentialDefinitionId, value: 'John', referent: '1', }, ], predicates: [ { - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + credentialDefinitionId: legacyCredentialDefinitionId, name: 'age', predicate: '>=', threshold: 18, diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts index 18bd9cfaab..90b54b0637 100644 --- a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -19,6 +19,15 @@ import type { AgentContext } from '@aries-framework/core' import { Hasher, TypedArrayEncoder } from '@aries-framework/core' import BigNumber from 'bn.js' +import { + getDidIndyCredentialDefinitionId, + getDidIndySchemaId, + getLegacyCredentialDefinitionId, + getLegacySchemaId, + parseSchemaId, +} from '../../indy-sdk/src/anoncreds/utils/identifiers' +import { parseIndyDid } from '../../indy-sdk/src/dids/didIndyUtil' + /** * In memory implementation of the {@link AnonCredsRegistry} interface. Useful for testing. */ @@ -26,7 +35,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { // Roughly match that the identifier starts with an unqualified indy did. Once the // anoncreds tests are not based on the indy-sdk anymore, we can use any identifier // we want, but the indy-sdk is picky about the identifier format. - public readonly supportedIdentifier = /^[a-zA-Z0-9]{21,22}/ + public readonly supportedIdentifier = /.+/ private schemas: Record private credentialDefinitions: Record @@ -52,7 +61,11 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { public async getSchema(agentContext: AgentContext, schemaId: string): Promise { const schema = this.schemas[schemaId] - const indyLedgerSeqNo = getSeqNoFromSchemaId(schemaId) + + const parsed = parseSchemaId(schemaId) + + const legaycSchemaId = getLegacySchemaId(parsed.didIdentifier, parsed.schemaName, parsed.schemaVersion) + const indyLedgerSeqNo = getSeqNoFromSchemaId(legaycSchemaId) if (!schema) { return { @@ -81,10 +94,17 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { agentContext: AgentContext, options: RegisterSchemaOptions ): Promise { - const schemaId = `${options.schema.issuerId}:2:${options.schema.name}:${options.schema.version}` - const indyLedgerSeqNo = getSeqNoFromSchemaId(schemaId) + const { id: didIdentifier, namespace } = parseIndyDid(options.schema.issuerId) + const didIndySchemaId = getDidIndySchemaId(namespace, didIdentifier, options.schema.name, options.schema.version) + const legacySchemaId = getLegacySchemaId(didIdentifier, options.schema.name, options.schema.version) - this.schemas[schemaId] = options.schema + const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) + + this.schemas[didIndySchemaId] = options.schema + this.schemas[legacySchemaId] = { + ...options.schema, + issuerId: didIdentifier, + } return { registrationMetadata: {}, @@ -96,7 +116,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { schemaState: { state: 'finished', schema: options.schema, - schemaId, + schemaId: didIndySchemaId, }, } } @@ -130,10 +150,34 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { agentContext: AgentContext, options: RegisterCredentialDefinitionOptions ): Promise { - const indyLedgerSeqNo = getSeqNoFromSchemaId(options.credentialDefinition.schemaId) - const credentialDefinitionId = `${options.credentialDefinition.issuerId}:3:CL:${indyLedgerSeqNo}:${options.credentialDefinition.tag}` - - this.credentialDefinitions[credentialDefinitionId] = options.credentialDefinition + const parsedSchema = parseSchemaId(options.credentialDefinition.schemaId) + const legacySchemaId = getLegacySchemaId( + parsedSchema.didIdentifier, + parsedSchema.schemaName, + parsedSchema.schemaVersion + ) + const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) + + const { id: didIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + + const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + didIdentifier, + indyLedgerSeqNo, + options.credentialDefinition.tag + ) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( + didIdentifier, + indyLedgerSeqNo, + options.credentialDefinition.tag + ) + + this.credentialDefinitions[didIndyCredentialDefinitionId] = options.credentialDefinition + this.credentialDefinitions[legacyCredentialDefinitionId] = { + ...options.credentialDefinition, + issuerId: didIdentifier, + schemaId: legacySchemaId, + } return { registrationMetadata: {}, @@ -141,7 +185,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { credentialDefinitionState: { state: 'finished', credentialDefinition: options.credentialDefinition, - credentialDefinitionId, + credentialDefinitionId: didIndyCredentialDefinitionId, }, } } diff --git a/packages/anoncreds/tests/anoncreds.test.ts b/packages/anoncreds/tests/anoncreds.test.ts index 127bbee586..1f4c32f973 100644 --- a/packages/anoncreds/tests/anoncreds.test.ts +++ b/packages/anoncreds/tests/anoncreds.test.ts @@ -123,7 +123,7 @@ describe('AnonCreds API', () => { options: {}, schema: { attrNames: ['name', 'age'], - issuerId: '6xDN7v3AiGgusRp4bqZACZ', + issuerId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ', name: 'Employee Credential', version: '1.0.0', }, @@ -136,11 +136,11 @@ describe('AnonCreds API', () => { state: 'finished', schema: { attrNames: ['name', 'age'], - issuerId: '6xDN7v3AiGgusRp4bqZACZ', + issuerId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ', name: 'Employee Credential', version: '1.0.0', }, - schemaId: '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0', + schemaId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ/anoncreds/v0/SCHEMA/Employee Credential/1.0.0', }, }) @@ -148,22 +148,22 @@ describe('AnonCreds API', () => { const anonCredsSchemaRepository = agent.dependencyManager.resolve(AnonCredsSchemaRepository) const schemaRecord = await anonCredsSchemaRepository.getBySchemaId( agent.context, - '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0' + 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ/anoncreds/v0/SCHEMA/Employee Credential/1.0.0' ) expect(schemaRecord).toMatchObject({ - schemaId: '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0', + schemaId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ/anoncreds/v0/SCHEMA/Employee Credential/1.0.0', schema: { attrNames: ['name', 'age'], - issuerId: '6xDN7v3AiGgusRp4bqZACZ', + issuerId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ', name: 'Employee Credential', version: '1.0.0', }, }) expect(schemaRecord.getTags()).toEqual({ - schemaId: '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0', - issuerId: '6xDN7v3AiGgusRp4bqZACZ', + schemaId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ/anoncreds/v0/SCHEMA/Employee Credential/1.0.0', + issuerId: 'did:indy:pool:localtest:6xDN7v3AiGgusRp4bqZACZ', schemaName: 'Employee Credential', schemaVersion: '1.0.0', }) @@ -192,7 +192,7 @@ describe('AnonCreds API', () => { keyType: KeyType.Ed25519, }) - const issuerId = 'VsKV7grR1BUE29mG2Fm2kX' + const issuerId = 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX' const credentialDefinitionResult = await agent.modules.anoncreds.registerCredentialDefinition({ credentialDefinition: { @@ -209,7 +209,7 @@ describe('AnonCreds API', () => { credentialDefinitionState: { state: 'finished', credentialDefinition: { - issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + issuerId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX', tag: 'TAG', schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', type: 'CL', @@ -227,7 +227,7 @@ describe('AnonCreds API', () => { }, }, }, - credentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + credentialDefinitionId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX/anoncreds/v0/CLAIM_DEF/75206/TAG', }, }) @@ -237,13 +237,13 @@ describe('AnonCreds API', () => { ) const credentialDefinitionRecord = await anonCredsCredentialDefinitionRepository.getByCredentialDefinitionId( agent.context, - 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG' + 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX/anoncreds/v0/CLAIM_DEF/75206/TAG' ) expect(credentialDefinitionRecord).toMatchObject({ - credentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + credentialDefinitionId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX/anoncreds/v0/CLAIM_DEF/75206/TAG', credentialDefinition: { - issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + issuerId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX', tag: 'TAG', schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', type: 'CL', @@ -264,9 +264,9 @@ describe('AnonCreds API', () => { }) expect(credentialDefinitionRecord.getTags()).toEqual({ - credentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', + credentialDefinitionId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX/anoncreds/v0/CLAIM_DEF/75206/TAG', schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', - issuerId: 'VsKV7grR1BUE29mG2Fm2kX', + issuerId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX', tag: 'TAG', }) }) diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index a2b5e339ac..5895e5c075 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -53,6 +53,12 @@ import { IndySdkModule, IndySdkSovDidResolver, } from '../../indy-sdk/src' +import { + getLegacyCredentialDefinitionId, + getLegacySchemaId, + parseCredentialDefinitionId, + parseSchemaId, +} from '../../indy-sdk/src/anoncreds/utils/identifiers' import { getIndySdkModuleConfig } from '../../indy-sdk/tests/setupIndySdkModule' import { IndyVdrAnonCredsRegistry, IndyVdrSovDidResolver, IndyVdrModule } from '../../indy-vdr/src' import { @@ -445,14 +451,15 @@ export async function setupAnonCredsTests< export async function prepareForAnonCredsIssuance(agent: Agent, { attributeNames }: { attributeNames: string[] }) { // Add existing endorser did to the wallet - const issuerId = await importExistingIndyDidFromPrivateKey(agent, TypedArrayEncoder.fromString(publicDidSeed)) + const unqualifiedDid = await importExistingIndyDidFromPrivateKey(agent, TypedArrayEncoder.fromString(publicDidSeed)) + const didIndyDid = `did:indy:pool:localtest:${unqualifiedDid}` const schema = await registerSchema(agent, { // TODO: update attrNames to attributeNames attrNames: attributeNames, name: `Schema ${randomUUID()}`, version: '1.0', - issuerId, + issuerId: didIndyDid, }) // Wait some time pass to let ledger settle the object @@ -460,16 +467,31 @@ export async function prepareForAnonCredsIssuance(agent: Agent, { attributeNames const credentialDefinition = await registerCredentialDefinition(agent, { schemaId: schema.schemaId, - issuerId, + issuerId: didIndyDid, tag: 'default', }) + const s = parseSchemaId(schema.schemaId) + const cd = parseCredentialDefinitionId(credentialDefinition.credentialDefinitionId) + + const legacySchemaId = getLegacySchemaId(s.didIdentifier, s.schemaName, s.schemaVersion) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.didIdentifier, cd.schemaSeqNo, cd.tag) + // Wait some time pass to let ledger settle the object await sleep(1000) + // NOTE: we return the legacy schema and credential definition ids here because that's what currently expected + // in all tests. If we also support did:indy in tests we probably want to return the qualified identifiers here + // and transform them to the legacy variant in the specific tests that need it. return { - schema, - credentialDefinition, + schema: { + ...schema, + schemaId: legacySchemaId, + }, + credentialDefinition: { + ...credentialDefinition, + credentialDefinitionId: legacyCredentialDefinitionId, + }, } } @@ -479,9 +501,7 @@ async function registerSchema( ): Promise { const { schemaState } = await agent.modules.anoncreds.registerSchema({ schema, - options: { - didIndyNamespace: 'pool:localtest', - }, + options: {}, }) testLogger.test(`created schema with id ${schemaState.schemaId}`, schema) @@ -501,9 +521,7 @@ async function registerCredentialDefinition( ): Promise { const { credentialDefinitionState } = await agent.modules.anoncreds.registerCredentialDefinition({ credentialDefinition, - options: { - didIndyNamespace: 'pool:localtest', - }, + options: {}, }) if (credentialDefinitionState.state !== 'finished') { diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index e00718c62c..d54a46a016 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -31,7 +31,7 @@ import { AriesFrameworkError, injectable, inject, utils } from '@aries-framework import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' import { assertIndySdkWallet } from '../../utils/assertIndySdkWallet' -import { getIndySeqNoFromUnqualifiedCredentialDefinitionId } from '../utils/identifiers' +import { parseCredentialDefinitionId } from '../utils/identifiers' import { indySdkCredentialDefinitionFromAnonCreds, indySdkRevocationRegistryDefinitionFromAnonCreds, @@ -105,8 +105,8 @@ export class IndySdkHolderService implements AnonCredsHolderService { ) // Get the seqNo for the schemas so we can use it when transforming the schemas - const schemaSeqNo = getIndySeqNoFromUnqualifiedCredentialDefinitionId(credentialDefinitionId) - seqNoMap[credentialDefinition.schemaId] = schemaSeqNo + const { schemaSeqNo } = parseCredentialDefinitionId(credentialDefinitionId) + seqNoMap[credentialDefinition.schemaId] = Number(schemaSeqNo) } // Convert AnonCreds schemas to Indy schemas diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts index d0d0a796d1..2b5365522b 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts @@ -15,9 +15,11 @@ import type { AgentContext } from '@aries-framework/core' import { generateLegacyProverDidLikeString } from '@aries-framework/anoncreds' import { injectable, AriesFrameworkError, inject } from '@aries-framework/core' +import { parseIndyDid } from '../../dids/didIndyUtil' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' import { assertIndySdkWallet } from '../../utils/assertIndySdkWallet' +import { getLegacySchemaId } from '../utils/identifiers' import { createTailsReader } from '../utils/tails' import { indySdkSchemaFromAnonCreds } from '../utils/transform' @@ -30,11 +32,14 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { } public async createSchema(agentContext: AgentContext, options: CreateSchemaOptions): Promise { - const { issuerId, name, version, attrNames } = options + // We only support passing qualified did:indy issuer ids in the indy issuer service when creating objects + const { id: unqualifiedDid } = parseIndyDid(options.issuerId) + + const { name, version, attrNames, issuerId } = options assertIndySdkWallet(agentContext.wallet) try { - const [, schema] = await this.indySdk.issuerCreateSchema(issuerId, name, version, attrNames) + const [, schema] = await this.indySdk.issuerCreateSchema(unqualifiedDid, name, version, attrNames) return { issuerId, @@ -54,6 +59,12 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { ): Promise { const { tag, supportRevocation, schema, issuerId, schemaId } = options + // We only support passing qualified did:indy issuer ids in the indy issuer service when creating objects + const { id: unqualifiedDid } = parseIndyDid(options.issuerId) + + // parse schema in a way that supports both unqualified and qualified identifiers + const legacySchemaId = getLegacySchemaId(unqualifiedDid, schema.name, schema.version) + if (!metadata) throw new AriesFrameworkError('The metadata parameter is required when using Indy, but received undefined.') @@ -61,8 +72,8 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { assertIndySdkWallet(agentContext.wallet) const [, credentialDefinition] = await this.indySdk.issuerCreateAndStoreCredentialDef( agentContext.wallet.handle, - issuerId, - indySdkSchemaFromAnonCreds(schemaId, schema, metadata.indyLedgerSchemaSeqNo), + unqualifiedDid, + indySdkSchemaFromAnonCreds(legacySchemaId, schema, metadata.indyLedgerSchemaSeqNo), tag, 'CL', { diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts index e4e4cb1d2d..b280256229 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts @@ -6,7 +6,7 @@ import { inject, injectable } from '@aries-framework/core' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' -import { getIndySeqNoFromUnqualifiedCredentialDefinitionId } from '../utils/identifiers' +import { parseCredentialDefinitionId } from '../utils/identifiers' import { indySdkCredentialDefinitionFromAnonCreds, indySdkRevocationRegistryDefinitionFromAnonCreds, @@ -39,8 +39,8 @@ export class IndySdkVerifierService implements AnonCredsVerifierService { ) // Get the seqNo for the schemas so we can use it when transforming the schemas - const schemaSeqNo = getIndySeqNoFromUnqualifiedCredentialDefinitionId(credentialDefinitionId) - seqNoMap[credentialDefinition.schemaId] = schemaSeqNo + const { schemaSeqNo } = parseCredentialDefinitionId(credentialDefinitionId) + seqNoMap[credentialDefinition.schemaId] = Number(schemaSeqNo) } // Convert AnonCreds schemas to Indy schemas diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts index 4dba3a8e17..ca1751c4e2 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -22,7 +22,9 @@ describe('identifiers', () => { const anotherId = 'some:id' - expect(indySdkAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) + // unqualified issuerId not in regex on purpose. See note in implementation. + expect(indySdkAnonCredsRegistryIdentifierRegex.test(did)).toEqual(false) + expect(indySdkAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) expect(indySdkAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) expect(indySdkAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) 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 7930bfb2fb..f94060d8fd 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/transform.test.ts @@ -12,7 +12,7 @@ describe('transform', () => { it('anonCredsSchemaFromIndySdk should return a valid anoncreds schema', () => { const schema: Schema = { attrNames: ['hello'], - id: '12345:2:Example Schema:1.0.0', + id: 'TL1EaPFCZ8Si5aUrqScBDt:2:Example Schema:1.0.0', name: 'Example Schema', seqNo: 150, ver: '1.0', @@ -21,24 +21,24 @@ describe('transform', () => { expect(anonCredsSchemaFromIndySdk(schema)).toEqual({ attrNames: ['hello'], - issuerId: '12345', + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', name: 'Example Schema', version: '1.0.0', }) }) it('indySdkSchemaFromAnonCreds should return a valid indy sdk schema', () => { - const schemaId = '12345:2:Example Schema:1.0.0' + const schemaId = 'TL1EaPFCZ8Si5aUrqScBDt:2:Example Schema:1.0.0' const schema: AnonCredsSchema = { attrNames: ['hello'], - issuerId: '12345', + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', name: 'Example Schema', version: '1.0.0', } expect(indySdkSchemaFromAnonCreds(schemaId, schema, 150)).toEqual({ attrNames: ['hello'], - id: '12345:2:Example Schema:1.0.0', + id: 'TL1EaPFCZ8Si5aUrqScBDt:2:Example Schema:1.0.0', name: 'Example Schema', seqNo: 150, ver: '1.0', @@ -48,7 +48,7 @@ describe('transform', () => { it('anonCredsCredentialDefinitionFromIndySdk should return a valid anoncreds credential definition', () => { const credDef: CredDef = { - id: '12345:3:CL:420:someTag', + id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:420:someTag', schemaId: '8910:2:Example Schema:1.0.0', tag: 'someTag', type: 'CL', @@ -61,7 +61,7 @@ describe('transform', () => { } expect(anonCredsCredentialDefinitionFromIndySdk(credDef)).toEqual({ - issuerId: '12345', + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', schemaId: '8910:2:Example Schema:1.0.0', tag: 'someTag', type: 'CL', @@ -74,9 +74,9 @@ describe('transform', () => { }) it('indySdkCredentialDefinitionFromAnonCreds should return a valid indy sdk credential definition', () => { - const credentialDefinitionId = '12345:3:CL:420:someTag' + const credentialDefinitionId = 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:420:someTag' const credentialDefinition: AnonCredsCredentialDefinition = { - issuerId: '12345', + issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', schemaId: '8910:2:Example Schema:1.0.0', tag: 'someTag', type: 'CL', @@ -88,7 +88,7 @@ describe('transform', () => { } expect(indySdkCredentialDefinitionFromAnonCreds(credentialDefinitionId, credentialDefinition)).toEqual({ - id: '12345:3:CL:420:someTag', + id: 'TL1EaPFCZ8Si5aUrqScBDt:3:CL:420:someTag', schemaId: '8910:2:Example Schema:1.0.0', tag: 'someTag', type: 'CL', diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index d80ed1de8d..fa8b961890 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -34,8 +34,11 @@ const legacyIndyRevocationRegistryIdRegex = // combines both legacy and did:indy anoncreds identifiers and also the issuer id const indySdkAnonCredsRegexes = [ + // NOTE: we only include the qualified issuer id here, as we don't support registering objects based on legacy issuer ids. + // you can still resolve using legacy issuer ids, but you need to use the full did:indy identifier when registering. + // As we find a matching anoncreds registry based on the issuerId only when creating an object, this will make sure + // it will throw an no registry found for identifier error. // issuer id - legacyIndyIssuerIdRegex, DID_INDY_REGEX, // schema diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts index c9bb1bbc93..6f4eabab97 100644 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkSovDidResolver.test.ts @@ -23,6 +23,10 @@ mockFunction(indySdkPoolServiceMock.getPoolForNamespace).mockReturnValue({ config: { indyNamespace: 'pool1' }, } as IndySdkPool) +mockFunction(indySdkPoolServiceMock.getPoolForDid).mockResolvedValue({ + pool: { config: { indyNamespace: 'pool1' } } as IndySdkPool, +}) + const agentConfig = getAgentConfig('IndySdkSovDidResolver') const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) diff --git a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts index c2401e41a3..77e034941d 100644 --- a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts @@ -1,4 +1,4 @@ -import { Agent, TypedArrayEncoder } from '@aries-framework/core' +import { Agent, Key, KeyType, TypedArrayEncoder } from '@aries-framework/core' import { agentDependencies, @@ -45,6 +45,7 @@ describe('IndySdkAnonCredsRegistry', () => { const dynamicVersion = `1.${Math.random() * 100}` const legacyIssuerId = 'TL1EaPFCZ8Si5aUrqScBDt' + const signingKey = Key.fromPublicKeyBase58('FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', KeyType.Ed25519) const didIndyIssuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' const legacySchemaId = `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}` @@ -59,7 +60,6 @@ describe('IndySdkAnonCredsRegistry', () => { }, options: {}, }) - console.log(schemaResult) expect(schemaResult).toMatchObject({ schemaState: { @@ -212,7 +212,7 @@ describe('IndySdkAnonCredsRegistry', () => { ver: '1.0', }) - await indySdkPoolService.submitWriteRequest(agent.context, pool, revocationRegistryRequest, legacyIssuerId) + await indySdkPoolService.submitWriteRequest(agent.context, pool, revocationRegistryRequest, signingKey) const legacyRevocationRegistryDefinition = await indySdkAnonCredsRegistry.getRevocationRegistryDefinition( agent.context, @@ -292,7 +292,7 @@ describe('IndySdkAnonCredsRegistry', () => { protocolVersion: 2, reqId: Math.floor(Math.random() * 1000000), }, - legacyIssuerId + signingKey ) const legacyRevocationStatusList = await indySdkAnonCredsRegistry.getRevocationStatusList( From 33d87bf225c4225c0d59d42d523bb6e999a76a93 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 28 Feb 2023 14:54:37 +0100 Subject: [PATCH 03/11] temp Signed-off-by: Timo Glastra --- .../services/IndySdkAnonCredsRegistry.ts | 7 +- .../src/anoncreds/utils/identifiers.ts | 7 +- .../src/dids/IndySdkSovDidResolver.ts | 25 +- .../indy-sdk-anoncreds-registry.e2e.test.ts | 4 +- .../tests/sov-did-resolver.e2e.test.ts | 2 +- .../src/anoncreds/IndyVdrAnonCredsRegistry.ts | 335 ++++++++---------- .../utils/_tests_/identifier.test.ts | 52 --- .../utils/_tests_/identifiers.test.ts | 185 ++++++++++ .../src/anoncreds/utils/identifiers.ts | 160 +++++++-- .../src/dids/IndyVdrIndyDidRegistrar.ts | 152 +++++--- .../src/dids/IndyVdrIndyDidResolver.ts | 76 ++-- .../src/dids/IndyVdrSovDidResolver.ts | 33 +- packages/indy-vdr/src/dids/didIndyUtil.ts | 29 ++ .../indy-vdr/src/pool/IndyVdrPoolService.ts | 24 +- .../indy-vdr/tests/__fixtures__/anoncreds.ts | 30 ++ packages/indy-vdr/tests/helpers.ts | 31 +- .../indy-vdr-anoncreds-registry.e2e.test.ts | 309 ++++++++-------- .../tests/indy-vdr-did-resolver.e2e.test.ts | 300 ++++++++-------- 18 files changed, 1043 insertions(+), 718 deletions(-) delete mode 100644 packages/indy-vdr/src/anoncreds/utils/_tests_/identifier.test.ts create mode 100644 packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts create mode 100644 packages/indy-vdr/tests/__fixtures__/anoncreds.ts diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index fe0d30e35b..4a3a77b522 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -31,12 +31,9 @@ import { } from '../utils/identifiers' import { anonCredsRevocationStatusListFromIndySdk } from '../utils/transform' -/** - * TODO: validation of the identifiers. The Indy SDK classes only support the legacy (unqualified) identifiers. - */ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { /** - * This class only supports resolving and registering objects with legacy indy identifiers. + * This class supports resolving and registering objects with did:indy as well as legacy indy identifiers. * It needs to include support for the schema, credential definition, revocation registry as well * as the issuer id (which is needed when registering objects). */ @@ -290,7 +287,6 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { options.credentialDefinition ) - // TODO: check structure of the schemaId // TODO: this will bypass caching if done on a higher level. const { schema, schemaMetadata, resolutionMetadata } = await this.getSchema( agentContext, @@ -334,7 +330,6 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { }) const submitterKey = await verificationKeyForIndyDid(agentContext, options.credentialDefinition.issuerId) - const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterKey) agentContext.config.logger.debug( diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index fa8b961890..6a847f321c 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -1,6 +1,9 @@ -import { DID_INDY_REGEX } from '../../utils/did' +/** + * NOTE: this file is availalbe in both the indy-sdk and indy-vdr packages. If making changes to + * this file, make sure to update both files if applicable. + */ -const legacyIndyIssuerIdRegex = /^[a-zA-Z0-9]{21,22}$/ +import { DID_INDY_REGEX } from '../../utils/did' const didIndyAnonCredsBase = /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts index bbbeec71f8..ff6afc9571 100644 --- a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts @@ -23,7 +23,10 @@ export class IndySdkSovDidResolver implements DidResolver { const keyAgreementId = `${parsed.did}#key-agreement-1` const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) - addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) + + if (endpoints) { + addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) + } return { didDocument: builder.build(), @@ -52,35 +55,39 @@ export class IndySdkSovDidResolver implements DidResolver { return await indySdk.parseGetNymResponse(response) } - private async getEndpointsForDid(agentContext: AgentContext, pool: IndySdkPool, did: string) { + private async getEndpointsForDid(agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string) { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) try { - agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.didIndyNamespace}'`) + agentContext.config.logger.debug( + `Get endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'` + ) - const request = await indySdk.buildGetAttribRequest(null, did, 'endpoint', null, null) + const request = await indySdk.buildGetAttribRequest(null, unqualifiedDid, 'endpoint', null, null) agentContext.config.logger.debug( - `Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.didIndyNamespace}'` + `Submitting get endpoint ATTRIB request for did '${unqualifiedDid}' to ledger '${pool.didIndyNamespace}'` ) const response = await indySdkPoolService.submitReadRequest(pool, request) - if (!response.result.data) return {} + if (!response.result.data) return null const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib agentContext.config.logger.debug( - `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.didIndyNamespace}'`, + `Got endpoints '${JSON.stringify(endpoints)}' for did '${unqualifiedDid}' from ledger '${ + pool.didIndyNamespace + }'`, { response, endpoints, } ) - return endpoints ?? {} + return endpoints ?? null } catch (error) { agentContext.config.logger.error( - `Error retrieving endpoints for did '${did}' from ledger '${pool.didIndyNamespace}'`, + `Error retrieving endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'`, { error, } diff --git a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts index 77e034941d..41de4d9c65 100644 --- a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts @@ -136,7 +136,7 @@ describe('IndySdkAnonCredsRegistry', () => { tag: 'TAG', schemaId: didIndySchemaId, type: 'CL', - value: {}, + value: credentialDefinitionValue, }, credentialDefinitionId: didIndyCredentialDefinitionId, state: 'finished', @@ -188,8 +188,6 @@ describe('IndySdkAnonCredsRegistry', () => { resolutionMetadata: {}, }) - assertIndySdkWallet(agent.context.wallet) - // We don't support creating a revocation registry using AFJ yet, so we directly use indy-sdk to register the revocation registry const legacyRevocationRegistryId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` const didIndyRevocationRegistryId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` diff --git a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts index ef220a59a3..714faac7ca 100644 --- a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -19,7 +19,7 @@ describe('Indy SDK Sov DID resolver', () => { await agent.wallet.delete() }) - it('should resolve a did:sov did', async () => { + test('resolve a did:sov did', async () => { // Add existing endorser did to the wallet const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( agent, diff --git a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts index 0f492edcb2..f185eb3f2e 100644 --- a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts +++ b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts @@ -8,10 +8,10 @@ import type { RegisterCredentialDefinitionReturn, GetRevocationStatusListReturn, GetRevocationRegistryDefinitionReturn, + AnonCredsRevocationRegistryDefinition, } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' -import { DidsApi, getKeyDidMappingByVerificationMethod } from '@aries-framework/core' import { GetSchemaRequest, SchemaRequest, @@ -22,15 +22,19 @@ import { GetRevocationRegistryDefinitionRequest, } from '@hyperledger/indy-vdr-shared' +import { parseIndyDid, verificationKeyForIndyDid } from '../dids/didIndyUtil' import { IndyVdrPoolService } from '../pool' import { - didFromSchemaId, - didFromCredentialDefinitionId, - didFromRevocationRegistryDefinitionId, getLegacySchemaId, getLegacyCredentialDefinitionId, indyVdrAnonCredsRegistryIdentifierRegex, + parseSchemaId, + getDidIndySchemaId, + parseCredentialDefinitionId, + getDidIndyCredentialDefinitionId, + parseRevocationRegistryId, + getLegacyRevocationRegistryId, } from './utils/identifiers' import { anonCredsRevocationStatusListFromIndyVdr } from './utils/transform' @@ -41,12 +45,14 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { try { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const did = didFromSchemaId(schemaId) - - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) - + // parse schema id (supports did:indy and legacy) + const { did, didIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.indyNamespace}'`) - const request = new GetSchemaRequest({ submitterDid: did, schemaId }) + + // even though we support did:indy and legacy identifiers we always need to fetch using the legacy identifier + const legacySchemaId = getLegacySchemaId(didIdentifier, schemaName, schemaVersion) + const request = new GetSchemaRequest({ schemaId: legacySchemaId }) agentContext.config.logger.trace( `Submitting get schema request for schema '${schemaId}' to ledger '${pool.indyNamespace}'` @@ -57,36 +63,34 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { response, }) - const issuerId = didFromSchemaId(schemaId) + if (!('attr_names' in response.result.data)) { + agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`) - if ('attr_names' in response.result.data) { return { - schema: { - attrNames: response.result.data.attr_names, - name: response.result.data.name, - version: response.result.data.version, - issuerId, - }, - schemaId: schemaId, - resolutionMetadata: {}, - schemaMetadata: { - didIndyNamespace: pool.indyNamespace, - // 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: response.result.seqNo, + schemaId, + resolutionMetadata: { + error: 'notFound', + message: `unable to find schema with id ${schemaId}`, }, + schemaMetadata: {}, } } - agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`) - return { + schema: { + attrNames: response.result.data.attr_names, + name: response.result.data.name, + version: response.result.data.version, + issuerId: did, + }, schemaId, - resolutionMetadata: { - error: 'notFound', - message: `unable to find schema with id ${schemaId}`, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: pool.indyNamespace, + // 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: response.result.seqNo, }, - schemaMetadata: {}, } } catch (error) { agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`, { @@ -106,27 +110,28 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { public async registerSchema( agentContext: AgentContext, - options: IndyVdrRegisterSchemaOptions + options: RegisterSchemaOptions ): Promise { - if (!options.options.didIndyNamespace) { - return { - schemaMetadata: {}, - registrationMetadata: {}, - schemaState: { - reason: 'no didIndyNamespace defined in the options. didIndyNamespace is required when using the Indy VDR', - schema: options.schema, - state: 'failed', - }, - } - } - try { + // This will throw an error if trying to register a schema with a legacy indy identifier. We only support did:indy identifiers + // for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { id: unqualifiedDid, namespace } = parseIndyDid(options.schema.issuerId) + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + const pool = indyVdrPoolService.getPoolForNamespace(namespace) + agentContext.config.logger.debug( + `Register schema on ledger '${pool.indyNamespace}' with did '${options.schema.issuerId}'`, + options.schema + ) + + const didIndySchemaId = getDidIndySchemaId(namespace, unqualifiedDid, options.schema.name, options.schema.version) + const legacySchemaId = getLegacySchemaId(unqualifiedDid, options.schema.name, options.schema.version) + const schemaRequest = new SchemaRequest({ - submitterDid: options.schema.issuerId, + submitterDid: unqualifiedDid, schema: { - id: getLegacySchemaId(options.schema.issuerId, options.schema.name, options.schema.version), + id: legacySchemaId, name: options.schema.name, ver: '1.0', version: options.schema.version, @@ -134,30 +139,12 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { }, }) - const pool = indyVdrPoolService.getPoolForNamespace(options.options.didIndyNamespace) - - // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did - // from the ledger to know which key is associated with the did - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didResult = await didsApi.resolve(`did:sov:${options.schema.issuerId}`) - - if (!didResult.didDocument) { - return { - schemaMetadata: {}, - registrationMetadata: {}, - schemaState: { - schema: options.schema, - state: 'failed', - reason: `didNotFound: unable to resolve did did:sov:${options.schema.issuerId}: ${didResult.didResolutionMetadata.message}`, - }, - } - } - - const verificationMethod = didResult.didDocument.dereferenceKey(`did:sov:${options.schema.issuerId}#key-1`) - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) - const key = getKeyFromVerificationMethod(verificationMethod) - - const response = await pool.submitWriteRequest(agentContext, schemaRequest, key) + const submitterKey = await verificationKeyForIndyDid(agentContext, options.schema.issuerId) + const response = await pool.submitWriteRequest(agentContext, schemaRequest, submitterKey) + agentContext.config.logger.debug(`Registered schema '${didIndySchemaId}' on ledger '${pool.indyNamespace}'`, { + response, + schemaRequest, + }) return { schemaState: { @@ -168,14 +155,13 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { name: options.schema.name, version: options.schema.version, }, - schemaId: getLegacySchemaId(options.schema.issuerId, options.schema.name, options.schema.version), + schemaId: didIndySchemaId, }, registrationMetadata: {}, 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: response.result.txnMetadata.seqNo, - didIndyNamespace: pool.indyNamespace, }, } } catch (error) { @@ -204,53 +190,58 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { try { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const did = didFromCredentialDefinitionId(credentialDefinitionId) - - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) + // we support did:indy and legacy identifiers + const { did, didIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Getting credential definition '${credentialDefinitionId}' from ledger '${pool.indyNamespace}'` ) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, tag) const request = new GetCredentialDefinitionRequest({ - submitterDid: did, - credentialDefinitionId, + credentialDefinitionId: legacyCredentialDefinitionId, }) agentContext.config.logger.trace( `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.indyNamespace}'` ) - const response = await pool.submitReadRequest(request) - const schema = await this.fetchIndySchemaWithSeqNo(agentContext, response.result.ref, did) + // We need to fetch the schema to determine the schemaId (we only have the seqNo) + const schema = await this.fetchIndySchemaWithSeqNo(agentContext, response.result.ref, didIdentifier) + + if (!schema || !response.result.data) { + agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`) - if (response.result.data && schema) { return { - credentialDefinitionId: credentialDefinitionId, - credentialDefinition: { - issuerId: didFromCredentialDefinitionId(credentialDefinitionId), - schemaId: schema.schema.schemaId, - tag: response.result.tag, - type: 'CL', - value: response.result.data, - }, - credentialDefinitionMetadata: { - didIndyNamespace: pool.indyNamespace, + credentialDefinitionId, + credentialDefinitionMetadata: {}, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition with id ${credentialDefinitionId}`, }, - resolutionMetadata: {}, } } - agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`) + // Format the schema id based on the type of the credential definition id + const schemaId = credentialDefinitionId.startsWith('did:indy') + ? getDidIndySchemaId(pool.indyNamespace, didIdentifier, schema.schema.name, schema.schema.version) + : schema.schema.schemaId return { - credentialDefinitionId, - credentialDefinitionMetadata: {}, - resolutionMetadata: { - error: 'notFound', - message: `unable to resolve credential definition with id ${credentialDefinitionId}`, + credentialDefinitionId: credentialDefinitionId, + credentialDefinition: { + issuerId: did, + schemaId, + tag: response.result.tag, + type: 'CL', + value: response.result.data, }, + credentialDefinitionMetadata: { + didIndyNamespace: pool.indyNamespace, + }, + resolutionMetadata: {}, } } catch (error) { agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`, { @@ -271,26 +262,22 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { public async registerCredentialDefinition( agentContext: AgentContext, - options: IndyVdrRegisterCredentialDefinitionOptions + options: RegisterCredentialDefinitionOptions ): Promise { - // Make sure didIndyNamespace is passed - if (!options.options.didIndyNamespace) { - return { - credentialDefinitionMetadata: {}, - registrationMetadata: {}, - credentialDefinitionState: { - reason: 'no didIndyNamespace defined in the options. didIndyNamespace is required when using the Indy SDK', - credentialDefinition: options.credentialDefinition, - state: 'failed', - }, - } - } - try { + // This will throw an error if trying to register a credential defintion with a legacy indy identifier. We only support did:indy + // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { id: unqualifiedDid, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const pool = indyVdrPoolService.getPoolForNamespace(options.options.didIndyNamespace) + const pool = indyVdrPoolService.getPoolForNamespace(namespace) + agentContext.config.logger.debug( + `Registering credential definition on ledger '${pool.indyNamespace}' with did '${options.credentialDefinition.issuerId}'`, + options.credentialDefinition + ) + // TODO: this will bypass caching if done on a higher level. const { schema, schemaMetadata, resolutionMetadata } = await this.getSchema( agentContext, options.credentialDefinition.schemaId @@ -310,17 +297,23 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } } - const credentialDefinitionId = getLegacyCredentialDefinitionId( + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( options.credentialDefinition.issuerId, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag ) + const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + unqualifiedDid, + schemaMetadata.indyLedgerSeqNo, + options.credentialDefinition.tag + ) const credentialDefinitionRequest = new CredentialDefinitionRequest({ - submitterDid: options.credentialDefinition.issuerId, + submitterDid: unqualifiedDid, credentialDefinition: { ver: '1.0', - id: credentialDefinitionId, + id: legacyCredentialDefinitionId, schemaId: `${schemaMetadata.indyLedgerSeqNo}`, type: 'CL', tag: options.credentialDefinition.tag, @@ -328,33 +321,10 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { }, }) - // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did - // from the ledger to know which key is associated with the did - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didResult = await didsApi.resolve(`did:sov:${options.credentialDefinition.issuerId}`) - - if (!didResult.didDocument) { - return { - credentialDefinitionMetadata: {}, - registrationMetadata: {}, - credentialDefinitionState: { - credentialDefinition: options.credentialDefinition, - state: 'failed', - reason: `didNotFound: unable to resolve did did:sov:${options.credentialDefinition.issuerId}: ${didResult.didResolutionMetadata.message}`, - }, - } - } - - const verificationMethod = didResult.didDocument.dereferenceKey( - `did:sov:${options.credentialDefinition.issuerId}#key-1` - ) - const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) - const key = getKeyFromVerificationMethod(verificationMethod) - - const response = await pool.submitWriteRequest(agentContext, credentialDefinitionRequest, key) - + const submitterKey = await verificationKeyForIndyDid(agentContext, options.credentialDefinition.issuerId) + const response = await pool.submitWriteRequest(agentContext, credentialDefinitionRequest, submitterKey) agentContext.config.logger.debug( - `Registered credential definition '${credentialDefinitionId}' on ledger '${pool.indyNamespace}'`, + `Registered credential definition '${didIndyCredentialDefinitionId}' on ledger '${pool.indyNamespace}'`, { response, credentialDefinition: options.credentialDefinition, @@ -362,12 +332,10 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ) return { - credentialDefinitionMetadata: { - didIndyNamespace: pool.indyNamespace, - }, + credentialDefinitionMetadata: {}, credentialDefinitionState: { credentialDefinition: options.credentialDefinition, - credentialDefinitionId, + credentialDefinitionId: didIndyCredentialDefinitionId, state: 'finished', }, registrationMetadata: {}, @@ -400,23 +368,28 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ): Promise { try { const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const pool = await indySdkPoolService.getPoolForDid(agentContext, did) + const { did, didIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = + parseRevocationRegistryId(revocationRegistryDefinitionId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.indyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` ) + const legacyRevocationRegistryId = getLegacyRevocationRegistryId( + didIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) const request = new GetRevocationRegistryDefinitionRequest({ - submitterDid: did, - revocationRegistryId: revocationRegistryDefinitionId, + revocationRegistryId: legacyRevocationRegistryId, }) agentContext.config.logger.trace( `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` ) - const response = await pool.submitReadRequest(request) if (!response.result.data) { @@ -437,28 +410,39 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } } + agentContext.config.logger.trace( + `Got revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.indyNamespace}'`, + { + response, + } + ) + + const credentialDefinitionId = revocationRegistryDefinitionId.startsWith('did:indy:') + ? getDidIndyCredentialDefinitionId(pool.indyNamespace, didIdentifier, schemaSeqNo, credentialDefinitionTag) + : getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, credentialDefinitionTag) + const revocationRegistryDefinition = { issuerId: did, - revocDefType: response.result.data?.revocDefType, + revocDefType: response.result.data.revocDefType, value: { - maxCredNum: response.result.data?.value.maxCredNum, - tailsHash: response.result.data?.value.tailsHash, - tailsLocation: response.result.data?.value.tailsLocation, + maxCredNum: response.result.data.value.maxCredNum, + tailsHash: response.result.data.value.tailsHash, + tailsLocation: response.result.data.value.tailsLocation, publicKeys: { accumKey: { - z: response.result.data?.value.publicKeys.accumKey.z, + z: response.result.data.value.publicKeys.accumKey.z, }, }, }, - tag: response.result.data?.tag, - credDefId: response.result.data?.credDefId, - } + tag: response.result.data.tag, + credDefId: credentialDefinitionId, + } satisfies AnonCredsRevocationRegistryDefinition return { revocationRegistryDefinitionId, revocationRegistryDefinition, revocationRegistryDefinitionMetadata: { - issuanceType: response.result.data?.value.issuanceType, + issuanceType: response.result.data.value.issuanceType, didIndyNamespace: pool.indyNamespace, }, resolutionMetadata: {}, @@ -490,24 +474,29 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ): Promise { try { const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const did = didFromRevocationRegistryDefinitionId(revocationRegistryId) - const pool = await indySdkPoolService.getPoolForDid(agentContext, did) + const { did, didIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + parseRevocationRegistryId(revocationRegistryId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.indyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` ) + const legacyRevocationRegistryId = getLegacyRevocationRegistryId( + didIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) const request = new GetRevocationRegistryDeltaRequest({ - submitterDid: did, - revocationRegistryId, + revocationRegistryId: legacyRevocationRegistryId, toTs: timestamp, }) agentContext.config.logger.trace( `Submitting get revocation registry delta request for revocation registry '${revocationRegistryId}' to ledger` ) - const response = await pool.submitReadRequest(request) agentContext.config.logger.debug( @@ -545,9 +534,9 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } const revocationRegistryDelta = { - accum: response.result.data?.value.accum_to.value.accum, - issued: response.result.data?.value.issued, - revoked: response.result.data?.value.revoked, + accum: response.result.data.value.accum_to.value.accum, + issued: response.result.data.value.issued, + revoked: response.result.data.value.revoked, } return { @@ -585,7 +574,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { private async fetchIndySchemaWithSeqNo(agentContext: AgentContext, seqNo: number, did: string) { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting transaction with seqNo '${seqNo}' from ledger '${pool.indyNamespace}'`) // ledgerType 1 is domain ledger @@ -624,15 +613,3 @@ interface SchemaType { name: string } } - -export interface IndyVdrRegisterSchemaOptions extends RegisterSchemaOptions { - options: { - didIndyNamespace: string - } -} - -export interface IndyVdrRegisterCredentialDefinitionOptions extends RegisterCredentialDefinitionOptions { - options: { - didIndyNamespace: string - } -} diff --git a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifier.test.ts b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifier.test.ts deleted file mode 100644 index 62528a0075..0000000000 --- a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifier.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - getLegacySchemaId, - getLegacyCredentialDefinitionId, - didFromSchemaId, - didFromCredentialDefinitionId, - indyVdrAnonCredsRegistryIdentifierRegex, -} from '../identifiers' - -describe('identifiers', () => { - it('matches against a legacy indy did, schema id, credential definition id and revocation registry id', () => { - const did = '7Tqg6BwSSWapxgUDm9KKgg' - const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' - const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' - const revocationRegistryId = - 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' - - const anotherId = 'some:id' - - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) - }) - - it('getLegacySchemaId should return a valid schema Id', () => { - const did = '29347' - const name = 'starlinks' - const version = '321' - - expect(getLegacySchemaId(did, name, version)).toEqual(`29347:2:starlinks:321`) - }) - - it('getLegacyCredentialDefinition should return a valid Credential Id', () => { - const did = '15565' - const seqNo = 323 - const tag = 'indyTag' - expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('15565:3:CL:323:indyTag') - }) - - it('didFromSchemaId should return the valid did from the schema', () => { - const schemaId = '29347:2:starlinks:321' - - expect(didFromSchemaId(schemaId)).toEqual('29347') - }) - - it('didFromCredentialId should return the valid did from the schema', () => { - const credentialDefinitionId = '15565:3:CL:323:indyTag' - - expect(didFromCredentialDefinitionId(credentialDefinitionId)).toEqual('15565') - }) -}) diff --git a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts new file mode 100644 index 0000000000..19ff274a29 --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts @@ -0,0 +1,185 @@ +import { + getDidIndyCredentialDefinitionId, + getDidIndyRevocationRegistryId, + getDidIndySchemaId, + getLegacyCredentialDefinitionId, + getLegacyRevocationRegistryId, + getLegacySchemaId, + indyVdrAnonCredsRegistryIdentifierRegex, + parseCredentialDefinitionId, + parseRevocationRegistryId, + parseSchemaId, +} from '../identifiers' + +describe('identifiers', () => { + describe('indyVdrAnonCredsRegistryIdentifierRegex', () => { + test('matches against a legacy indy did, schema id, credential definition id and revocation registry id', () => { + const did = '7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' + const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' + const revocationRegistryId = + 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + + const anotherId = 'some:id' + + // unqualified issuerId not in regex on purpose. See note in implementation. + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(did)).toEqual(false) + + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + + test('matches against a did indy did, schema id, credential definition id and revocation registry id', () => { + const did = 'did:indy:local:7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0' + const credentialDefinitionId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID' + const revocationRegistryId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/REV_REG_DEF/100669/SCH Employee ID/1-1024' + + const anotherId = 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/SOME_DEF' + + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + }) + + test('getLegacySchemaId returns a valid schema id given a did, name, and version', () => { + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getLegacySchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + test('getLegacyCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) + + test('getLegacyRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getLegacyRevocationRegistryId(did, seqNo, credentialDefinitionTag, tag)).toEqual( + '12345:4:12345:3:CL:420:someTag:CL_ACCUM:anotherTag' + ) + }) + + test('getDidIndySchemaId returns a valid schema id given a did, name, and version', () => { + const namespace = 'sovrin:test' + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getDidIndySchemaId(namespace, did, name, version)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/SCHEMA/backbench/420' + ) + }) + + test('getDidIndyCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getDidIndyCredentialDefinitionId(namespace, did, seqNo, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/CLAIM_DEF/420/someTag' + ) + }) + + test('getDidIndyRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getDidIndyRevocationRegistryId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/REV_REG_DEF/420/someTag/anotherTag' + ) + }) + + describe('parseSchemaId', () => { + test('parses legacy schema id', () => { + expect(parseSchemaId('SDqTzbVuCowusqGBNbNDjH:2:schema-name:1.0')).toEqual({ + did: 'SDqTzbVuCowusqGBNbNDjH', + didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + schemaName: 'schema-name', + schemaVersion: '1.0', + }) + }) + + test('parses did:indy schema id', () => { + expect(parseSchemaId('did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0')).toEqual( + { + didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + did: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + schemaName: 'schema-name', + schemaVersion: '1.0', + namespace: 'bcovrin:test', + } + ) + }) + }) + + describe('parseCredentialDefinitionId', () => { + test('parses legacy credential definition id', () => { + expect(parseCredentialDefinitionId('TL1EaPFCZ8Si5aUrqScBDt:3:CL:10:TAG')).toEqual({ + did: 'TL1EaPFCZ8Si5aUrqScBDt', + didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + schemaSeqNo: '10', + tag: 'TAG', + }) + }) + + test('parses did:indy credential definition id', () => { + expect( + parseCredentialDefinitionId('did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/10/TAG') + ).toEqual({ + didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + did: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', + namespace: 'pool:localtest', + schemaSeqNo: '10', + tag: 'TAG', + }) + }) + }) + + describe('parseRevocationRegistryId', () => { + test('parses legacy revocation registry id', () => { + expect( + parseRevocationRegistryId('5nDyJVP1NrcPAttP3xwMB9:4:5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npdb:CL_ACCUM:TAG1') + ).toEqual({ + did: '5nDyJVP1NrcPAttP3xwMB9', + didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + schemaSeqNo: '56495', + credentialDefinitionTag: 'npdb', + revocationRegistryTag: 'TAG1', + }) + }) + + test('parses did:indy revocation registry id', () => { + expect( + parseRevocationRegistryId('did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1') + ).toEqual({ + namespace: 'sovrin', + didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + did: 'did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9', + schemaSeqNo: '56495', + credentialDefinitionTag: 'npdb', + revocationRegistryTag: 'TAG1', + }) + }) + }) +}) diff --git a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts index d242ca3461..337ad73d2b 100644 --- a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts @@ -1,42 +1,156 @@ -export const legacyIndyVdrIssuerIdRegex = /^[a-zA-Z0-9]{21,22}$/ -export const legacyIndyVdrSchemaIdRegex = /^[a-zA-Z0-9]{21,22}:2:.+:[0-9.]+$/ -export const legacyIndyVdrCredentialDefinitionIdRegex = - /^[a-zA-Z0-9]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+)):(.+)?$/ -export const legacyIndyVdrRevocationRegistryIdRegex = - /^[a-zA-Z0-9]{21,22}:4:[a-zA-Z0-9]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+))(:.+)?:CL_ACCUM:(.+$)/ +/** + * NOTE: this file is availalbe in both the indy-sdk and indy-vdr packages. If making changes to + * this file, make sure to update both files if applicable. + */ + +import { DID_INDY_REGEX } from '../../utils/did' + +const didIndyAnonCredsBase = + /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ + +// did:indy::/anoncreds/v0/SCHEMA// +const didIndySchemaIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/SCHEMA/(?.+)/(?[0-9.]+)$` +) + +// :2:: +const legacyIndySchemaIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ + +// did:indy::/anoncreds/v0/CLAIM_DEF// +const didIndyCredentialDefinitionIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/CLAIM_DEF/(?[1-9][0-9]*)/(?.+)$` +) + +// :3:CL:: +const legacyIndyCredentialDefinitionIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ + +// did:indy::/anoncreds/v0/REV_REG_DEF/// +const didIndyRevocationRegistryIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/REV_REG_DEF/(?[1-9][0-9]*)/(?.+)/(?.+)$` +) + +// :4::3:CL::CL_ACCUM: +const legacyIndyRevocationRegistryIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ + +// combines both legacy and did:indy anoncreds identifiers and also the issuer id +const indyVdrAnonCredsRegexes = [ + // NOTE: we only include the qualified issuer id here, as we don't support registering objects based on legacy issuer ids. + // you can still resolve using legacy issuer ids, but you need to use the full did:indy identifier when registering. + // As we find a matching anoncreds registry based on the issuerId only when creating an object, this will make sure + // it will throw an no registry found for identifier error. + // issuer id + DID_INDY_REGEX, + + // schema + didIndySchemaIdRegex, + legacyIndySchemaIdRegex, + + // credential definition + didIndyCredentialDefinitionIdRegex, + legacyIndyCredentialDefinitionIdRegex, + + // revocation registry + legacyIndyRevocationRegistryIdRegex, + didIndyRevocationRegistryIdRegex, +] export const indyVdrAnonCredsRegistryIdentifierRegex = new RegExp( - `${legacyIndyVdrIssuerIdRegex.source}|${legacyIndyVdrSchemaIdRegex.source}|${legacyIndyVdrCredentialDefinitionIdRegex.source}|${legacyIndyVdrRevocationRegistryIdRegex.source}` + indyVdrAnonCredsRegexes.map((r) => r.source.replace(/(\?<[a-zA-Z]+>)?/g, '')).join('|') ) +export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, name: string, version: string) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/SCHEMA/${name}/${version}` +} + export function getLegacySchemaId(unqualifiedDid: string, name: string, version: string) { return `${unqualifiedDid}:2:${name}:${version}` } -export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: number, tag: string) { +export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: string | number, tag: string) { return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` } -/** - * Extract did from schema id - */ -export function didFromSchemaId(schemaId: string) { - const [did] = schemaId.split(':') +export function getDidIndyCredentialDefinitionId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + tag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` +} - return did +// TZQuLp43UcYTdtc3HewcDz:4:TZQuLp43UcYTdtc3HewcDz:3:CL:98158:BaustellenzertifikateNU1:CL_ACCUM:1-100 +export function getLegacyRevocationRegistryId( + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `${unqualifiedDid}:4:${unqualifiedDid}:3:CL:${seqNo}:${credentialDefinitionTag}:CL_ACCUM:${revocationRegistryTag}` } -/** - * Extract did from credential definition id - */ -export function didFromCredentialDefinitionId(credentialDefinitionId: string) { - const [did] = credentialDefinitionId.split(':') +export function getDidIndyRevocationRegistryId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` +} + +interface ParsedSchemaId { + did: string + didIdentifier: string + schemaName: string + schemaVersion: string + namespace?: string +} + +export function parseSchemaId(schemaId: string) { + const match = schemaId.match(didIndySchemaIdRegex) ?? schemaId.match(legacyIndySchemaIdRegex) + + if (!match) throw new Error(`Invalid schema id: ${schemaId}`) - return did + return match.groups as unknown as ParsedSchemaId } -export function didFromRevocationRegistryDefinitionId(revocationRegistryId: string) { - const [did] = revocationRegistryId.split(':') +interface ParsedCredentialDefinitionId { + did: string + didIdentifier: string + schemaSeqNo: string + tag: string + namespace?: string +} + +export function parseCredentialDefinitionId(credentialDefinitionId: string) { + const match = + credentialDefinitionId.match(didIndyCredentialDefinitionIdRegex) ?? + credentialDefinitionId.match(legacyIndyCredentialDefinitionIdRegex) + + if (!match) throw new Error(`Invalid credential definition id: ${credentialDefinitionId}`) + + return match.groups as unknown as ParsedCredentialDefinitionId +} + +interface ParsedRevocationRegistryId { + did: string + didIdentifier: string + schemaSeqNo: string + credentialDefinitionTag: string + revocationRegistryTag: string + namespace?: string +} + +export function parseRevocationRegistryId(revocationRegistryId: string) { + const match = + revocationRegistryId.match(didIndyRevocationRegistryIdRegex) ?? + revocationRegistryId.match(legacyIndyRevocationRegistryIdRegex) + + if (!match) throw new Error(`Invalid revocation registry id: ${revocationRegistryId}`) - return did + return match.groups as unknown as ParsedRevocationRegistryId } diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts index 50735a67eb..241ff8e5dc 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts @@ -34,6 +34,7 @@ import { indyDidDocumentFromDid, parseIndyDid, isSelfCertifiedIndyDid, + verificationKeyForIndyDid, } from './didIndyUtil' import { endpointsAttribFromServices } from './didSovUtil' @@ -44,10 +45,10 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { const seed = options.secret?.seed const privateKey = options.secret?.privateKey - const { alias, role, submitterDid, submitterVerkey, services, useEndpointAttrib } = options.options - let verkey = options.options.verkey + const { alias, role, submitterDid, services, useEndpointAttrib } = options.options let did = options.did - let id + let didIdentifier: string + let verificationKey: Key const allowOne = [privateKey, seed, did].filter((e) => e !== undefined) if (allowOne.length > 1) { @@ -62,11 +63,12 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { } try { - const { namespace, id: submitterId } = parseIndyDid(submitterDid) + // Parse submitterDid and extract namespace based on the submitter did + const { namespace: submitterNamespace, id: submitterDidIdentifier } = parseIndyDid(submitterDid) + const submitterSigningKey = await verificationKeyForIndyDid(agentContext, submitterDid) if (did) { - id = parseIndyDid(did).id - if (!verkey) { + if (!options.options.verkey) { return { didDocumentMetadata: {}, didRegistrationMetadata: {}, @@ -76,21 +78,43 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { }, } } - if (!isSelfCertifiedIndyDid(did, verkey)) { - throw new Error(`Initial verkey ${verkey} does not match did ˇ${did}`) + + const { namespace, id } = parseIndyDid(did) + didIdentifier = id + verificationKey = Key.fromPublicKeyBase58(options.options.verkey, KeyType.Ed25519) + + if (!isSelfCertifiedIndyDid(did, options.options.verkey)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Initial verkey ${options.options.verkey} does not match did ˇ${did}`, + }, + } + } + + if (submitterNamespace !== namespace) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `The submitter did uses namespace ${submitterNamespace} and the did to register uses namespace ${namespace}. Namespaces must match.`, + }, + } } } else { // Create a new key and calculate did according to the rules for indy did method - const key = await agentContext.wallet.createKey({ privateKey, seed, keyType: KeyType.Ed25519 }) - const buffer = Hasher.hash(key.publicKey, 'sha2-256') + verificationKey = await agentContext.wallet.createKey({ privateKey, seed, keyType: KeyType.Ed25519 }) + const buffer = Hasher.hash(verificationKey.publicKey, 'sha2-256') - id = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) - verkey = key.publicKeyBase58 - did = `did:indy:${namespace}:${id}` + didIdentifier = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + did = `did:indy:${submitterNamespace}:${didIdentifier}` } // Create base did document - const didDocumentBuilder = indyDidDocumentFromDid(did, verkey) + const didDocumentBuilder = indyDidDocumentFromDid(did, verificationKey.publicKeyBase58) let diddocContent // Add services if object was passed @@ -108,7 +132,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { .addVerificationMethod({ controller: did, id: keyAgreementId, - publicKeyBase58: createKeyAgreementKey(verkey), + publicKeyBase58: createKeyAgreementKey(verificationKey.publicKeyBase58), type: 'X25519KeyAgreementKey2019', }) .addKeyAgreement(keyAgreementId) @@ -123,7 +147,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { // create diddocContent parameter based on the diff between the base and the resulting DID Document diddocContent = didDocDiff( didDocumentBuilder.build().toJSON(), - indyDidDocumentFromDid(did, verkey).build().toJSON() + indyDidDocumentFromDid(did, verificationKey.publicKeyBase58).build().toJSON() ) } } @@ -131,21 +155,30 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { // Build did document const didDocument = didDocumentBuilder.build() - const pool = agentContext.dependencyManager.resolve(IndyVdrPoolService).getPoolForNamespace(namespace) + const pool = agentContext.dependencyManager.resolve(IndyVdrPoolService).getPoolForNamespace(submitterNamespace) // If there are services and we are using legacy indy endpoint attrib, make sure they are suitable before registering the DID if (services && useEndpointAttrib) { const endpoints = endpointsAttribFromServices(services) - await this.registerPublicDid(agentContext, pool, submitterId, submitterVerkey, id, verkey, alias, role) - await this.setEndpointsForDid(agentContext, pool, verkey, id, endpoints) + await this.registerPublicDid( + agentContext, + pool, + submitterDidIdentifier, + submitterSigningKey, + didIdentifier, + verificationKey, + alias, + role + ) + await this.setEndpointsForDid(agentContext, pool, didIdentifier, verificationKey, endpoints) } else { await this.registerPublicDid( agentContext, pool, - submitterId, - submitterVerkey, - id, - verkey, + submitterDidIdentifier, + submitterSigningKey, + didIdentifier, + verificationKey, alias, role, diddocContent @@ -168,7 +201,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { return { didDocumentMetadata: {}, didRegistrationMetadata: { - didIndyNamespace: namespace, + didIndyNamespace: submitterNamespace, }, didState: { state: 'finished', @@ -222,41 +255,44 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { private async registerPublicDid( agentContext: AgentContext, pool: IndyVdrPool, - submitterDid: string, - submitterVerkey: string, - targetDid: string, - verkey: string, + unqualifiedSubmitterDid: string, + submitterSigningKey: Key, + unqualifiedDid: string, + signingKey: Key, alias?: string, role?: string, diddocContent?: Record ) { try { - agentContext.config.logger.debug(`Register public did '${targetDid}' on ledger '${pool}'`) + agentContext.config.logger.debug(`Register public did '${unqualifiedDid}' on ledger '${pool}'`) // FIXME: Add diddocContent when supported by indy-vdr if (diddocContent) { throw new IndyVdrError('diddocContent is not yet supported') } - const request = new NymRequest({ submitterDid, dest: targetDid, verkey, alias }) - - const signingKey = Key.fromPublicKeyBase58(submitterVerkey, KeyType.Ed25519) + const request = new NymRequest({ + submitterDid: unqualifiedSubmitterDid, + dest: unqualifiedDid, + verkey: signingKey.publicKeyBase58, + alias, + }) - const response = await pool.submitWriteRequest(agentContext, request, signingKey) + const response = await pool.submitWriteRequest(agentContext, request, submitterSigningKey) - agentContext.config.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.indyNamespace}'`, { + agentContext.config.logger.debug(`Registered public did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, { response, }) - return targetDid + return unqualifiedDid } catch (error) { agentContext.config.logger.error( - `Error registering public did '${targetDid}' on ledger '${pool.indyNamespace}'`, + `Error registering public did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, { error, - submitterDid, - targetDid, - verkey, + unqualifiedSubmitterDid, + unqualifiedDid, + signingKey, alias, role, pool: pool.indyNamespace, @@ -270,35 +306,39 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { private async setEndpointsForDid( agentContext: AgentContext, pool: IndyVdrPool, - submitterVerkey: string, - did: string, + unqualifiedDid: string, + signingKey: Key, endpoints: IndyEndpointAttrib ): Promise { try { - agentContext.config.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.indyNamespace}'`, endpoints) + agentContext.config.logger.debug( + `Set endpoints for did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, + endpoints + ) const request = new AttribRequest({ - submitterDid: did, - targetDid: did, + submitterDid: unqualifiedDid, + targetDid: unqualifiedDid, raw: JSON.stringify({ endpoint: endpoints }), }) - const signingKey = Key.fromPublicKeyBase58(submitterVerkey, KeyType.Ed25519) - const response = await pool.submitWriteRequest(agentContext, request, signingKey) agentContext.config.logger.debug( - `Successfully set endpoints for did '${did}' on ledger '${pool.indyNamespace}'`, + `Successfully set endpoints for did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, { response, endpoints, } ) } catch (error) { - agentContext.config.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.indyNamespace}'`, { - error, - did, - endpoints, - }) + agentContext.config.logger.error( + `Error setting endpoints for did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, + { + error, + unqualifiedDid, + endpoints, + } + ) throw new IndyVdrError(error) } @@ -314,16 +354,12 @@ export interface IndyVdrDidCreateOptions extends DidCreateOptions { role?: string services?: DidDocumentService[] useEndpointAttrib?: boolean - submitterDid: string - submitterVerkey: string verkey?: string + + submitterDid: string } secret?: { seed?: Buffer privateKey?: Buffer } } - -// TODO: Add Update and Deactivate -export type IndyVdrIndyDidUpdateOptions = never -export type IndyVdrIndyDidDeactivateOptions = never diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts index 6f6d40cbcf..02369f6fca 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts @@ -1,4 +1,5 @@ -import type { CommEndpointType, GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' +import type { GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' +import type { IndyVdrPool } from '../pool' import type { DidResolutionResult, DidResolver, AgentContext } from '@aries-framework/core' import { GetAttribRequest, GetNymRequest } from '@hyperledger/indy-vdr-shared' @@ -15,10 +16,8 @@ export class IndyVdrIndyDidResolver implements DidResolver { public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} try { - const nym = await this.getPublicDid(agentContext, did) - // Get DID Document from Get NYM response - const didDocument = await this.buildDidDocument(agentContext, nym, did) + const didDocument = await this.buildDidDocument(agentContext, did) return { didDocument, @@ -37,34 +36,37 @@ export class IndyVdrIndyDidResolver implements DidResolver { } } - private async buildDidDocument(agentContext: AgentContext, getNymResponseData: GetNymResponseData, did: string) { + private async buildDidDocument(agentContext: AgentContext, did: string) { + const { id: unqualifiedDid, namespace } = parseIndyDid(did) + + const poolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + const pool = poolService.getPoolForNamespace(namespace) + + const nym = await this.getPublicDid(pool, unqualifiedDid) + // Create base Did Document // For modern did:indy DIDs, we assume that GET_NYM is always a full verkey in base58. // For backwards compatibility, we accept a shortened verkey and convert it using previous convention - const verkey = getFullVerkey(did, getNymResponseData.verkey) + const verkey = getFullVerkey(unqualifiedDid, nym.verkey) const builder = indyDidDocumentFromDid(did, verkey) // If GET_NYM does not return any diddocContent, fallback to legacy GET_ATTRIB endpoint - if (!getNymResponseData.diddocContent) { + if (!nym.diddocContent) { const keyAgreementId = `${did}#key-agreement-1` - - const endpoints = await this.getEndpointsForDid(agentContext, did) + const endpoints = await this.getEndpointsForDid(agentContext, pool, unqualifiedDid) if (endpoints) { - // If there is at least a didcomm endpoint, generate and a key agreement key - const commTypes: CommEndpointType[] = ['endpoint', 'did-communication', 'DIDComm'] - if (commTypes.some((type) => endpoints.types?.includes(type))) { - builder - .addVerificationMethod({ - controller: did, - id: keyAgreementId, - publicKeyBase58: createKeyAgreementKey(getNymResponseData.verkey), - type: 'X25519KeyAgreementKey2019', - }) - .addKeyAgreement(keyAgreementId) - } + builder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: did, + id: keyAgreementId, + publicKeyBase58: createKeyAgreementKey(verkey), + type: 'X25519KeyAgreementKey2019', + }) + .addKeyAgreement(keyAgreementId) // Process endpoint attrib following the same rules as for did:sov addServicesFromEndpointsAttrib(builder, did, endpoints, keyAgreementId) @@ -72,41 +74,29 @@ export class IndyVdrIndyDidResolver implements DidResolver { return builder.build() } else { // Combine it with didDoc (TODO: Check if diddocContent is returned as a JSON object or a string) - return combineDidDocumentWithJson(builder.build(), getNymResponseData.diddocContent) + return combineDidDocumentWithJson(builder.build(), nym.diddocContent) } } - private async getPublicDid(agentContext: AgentContext, did: string) { - const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const { namespace, id } = parseIndyDid(did) - - const pool = indyVdrPoolService.getPoolForNamespace(namespace) - - const request = new GetNymRequest({ dest: id }) + private async getPublicDid(pool: IndyVdrPool, unqualifiedDid: string) { + const request = new GetNymRequest({ dest: unqualifiedDid }) const didResponse = await pool.submitReadRequest(request) if (!didResponse.result.data) { - throw new IndyVdrNotFoundError(`DID ${id} not found in indy namespace ${namespace}`) + throw new IndyVdrNotFoundError(`DID ${unqualifiedDid} not found in indy namespace ${pool.indyNamespace}`) } return JSON.parse(didResponse.result.data) as GetNymResponseData } - private async getEndpointsForDid(agentContext: AgentContext, did: string) { - const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const { namespace, id } = parseIndyDid(did) - - const pool = indyVdrPoolService.getPoolForNamespace(namespace) - + private async getEndpointsForDid(agentContext: AgentContext, pool: IndyVdrPool, unqualifiedDid: string) { try { - agentContext.config.logger.debug(`Get endpoints for did '${id}' from ledger '${pool.indyNamespace}'`) + agentContext.config.logger.debug(`Get endpoints for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`) - const request = new GetAttribRequest({ targetDid: id, raw: 'endpoint' }) + const request = new GetAttribRequest({ targetDid: unqualifiedDid, raw: 'endpoint' }) agentContext.config.logger.debug( - `Submitting get endpoint ATTRIB request for did '${id}' to ledger '${pool.indyNamespace}'` + `Submitting get endpoint ATTRIB request for did '${unqualifiedDid}' to ledger '${pool.indyNamespace}'` ) const response = await pool.submitReadRequest(request) @@ -116,7 +106,7 @@ export class IndyVdrIndyDidResolver implements DidResolver { const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib agentContext.config.logger.debug( - `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.indyNamespace}'`, + `Got endpoints '${JSON.stringify(endpoints)}' for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`, { response, endpoints, @@ -126,7 +116,7 @@ export class IndyVdrIndyDidResolver implements DidResolver { return endpoints } catch (error) { agentContext.config.logger.error( - `Error retrieving endpoints for did '${did}' from ledger '${pool.indyNamespace}'`, + `Error retrieving endpoints for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`, { error, } diff --git a/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts index 842707aaa1..dd4ecab222 100644 --- a/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts +++ b/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts @@ -1,4 +1,5 @@ import type { GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' +import type { IndyVdrPool } from '../pool' import type { DidResolutionResult, ParsedDid, DidResolver, AgentContext } from '@aries-framework/core' import { GetAttribRequest, GetNymRequest } from '@hyperledger/indy-vdr-shared' @@ -15,14 +16,19 @@ export class IndyVdrSovDidResolver implements DidResolver { const didDocumentMetadata = {} try { - const nym = await this.getPublicDid(agentContext, parsed.id) - const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const endpoints = await this.getEndpointsForDid(agentContext, parsed.id) + // FIXME: this actually fetches the did twice (if not cached), once for the pool and once for the nym + // we do not store the diddocContent in the pool cache currently so we need to fetch it again + // The logic is mostly to determine which pool to use for a did + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, parsed.id) + const nym = await this.getPublicDid(pool, parsed.id) + const endpoints = await this.getEndpointsForDid(agentContext, pool, parsed.id) - if (endpoints) { - const keyAgreementId = `${parsed.did}#key-agreement-1` + const keyAgreementId = `${parsed.did}#key-agreement-1` + const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + if (endpoints) { addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) } @@ -43,26 +49,17 @@ export class IndyVdrSovDidResolver implements DidResolver { } } - private async getPublicDid(agentContext: AgentContext, did: string) { - const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) - - const request = new GetNymRequest({ dest: did }) - + private async getPublicDid(pool: IndyVdrPool, unqualifiedDid: string) { + const request = new GetNymRequest({ dest: unqualifiedDid }) const didResponse = await pool.submitReadRequest(request) if (!didResponse.result.data) { - throw new IndyVdrNotFoundError(`DID ${did} not found`) + throw new IndyVdrNotFoundError(`DID ${unqualifiedDid} not found`) } return JSON.parse(didResponse.result.data) as GetNymResponseData } - private async getEndpointsForDid(agentContext: AgentContext, did: string) { - const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) - + private async getEndpointsForDid(agentContext: AgentContext, pool: IndyVdrPool, did: string) { try { agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.indyNamespace}'`) diff --git a/packages/indy-vdr/src/dids/didIndyUtil.ts b/packages/indy-vdr/src/dids/didIndyUtil.ts index 6644703169..b7c63051d1 100644 --- a/packages/indy-vdr/src/dids/didIndyUtil.ts +++ b/packages/indy-vdr/src/dids/didIndyUtil.ts @@ -1,8 +1,12 @@ +import type { AgentContext } from '@aries-framework/core' + import { AriesFrameworkError, convertPublicKeyToX25519, DidDocument, DidDocumentBuilder, + DidsApi, + getKeyDidMappingByVerificationMethod, Hasher, JsonTransformer, Key, @@ -163,3 +167,28 @@ export function indyDidFromNamespaceAndInitialKey(namespace: string, initialKey: return { did, id, verkey } } + +/** + * Fetches the verification key for a given did:indy did and returns the key as a {@link Key} object. + * + * @throws {@link AriesFrameworkError} if the did could not be resolved or the key could not be extracted + */ +export async function verificationKeyForIndyDid(agentContext: AgentContext, did: string) { + // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did + // from the ledger to know which key is associated with the did + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const didResult = await didsApi.resolve(did) + + if (!didResult.didDocument) { + throw new AriesFrameworkError( + `Could not resolve did ${did}. ${didResult.didResolutionMetadata.error} ${didResult.didResolutionMetadata.message}` + ) + } + + // did:indy dids MUST have a verificationMethod with #verkey + const verificationMethod = didResult.didDocument.dereferenceKey(`${did}#verkey`) + const { getKeyFromVerificationMethod } = getKeyDidMappingByVerificationMethod(verificationMethod) + const key = getKeyFromVerificationMethod(verificationMethod) + + return key +} diff --git a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts index 1ad1b0f80a..f648c2a49b 100644 --- a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts +++ b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts @@ -3,6 +3,8 @@ import type { GetNymResponse } from '@hyperledger/indy-vdr-shared' import { Logger, InjectionSymbols, injectable, inject, CacheModuleConfig } from '@aries-framework/core' import { GetNymRequest } from '@hyperledger/indy-vdr-shared' +import { request, response } from 'express' +import { async } from 'rxjs' import { IndyVdrModuleConfig } from '../IndyVdrModuleConfig' import { IndyVdrError, IndyVdrNotFoundError, IndyVdrNotConfiguredError } from '../error' @@ -36,8 +38,15 @@ export class IndyVdrPoolService { * If the did is a qualified indy did, the pool will be determined based on the namespace. * If it is a legacy unqualified indy did, the pool will be determined based on the algorithm as described in this document: * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit + * + * This method will optionally return a nym response when the did has been resolved to determine the ledger + * either now or in the past. The nymResponse can be used to prevent multiple ledger quries fetching the same + * did */ - public async getPoolForDid(agentContext: AgentContext, did: string): Promise { + public async getPoolForDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndyVdrPool; nymResponse?: CachedDidResponse['nymResponse'] }> { // Check if the did starts with did:indy const match = did.match(DID_INDY_REGEX) @@ -46,7 +55,7 @@ export class IndyVdrPoolService { const pool = this.getPoolForNamespace(namespace) - if (pool) return pool + if (pool) return { pool } throw new IndyVdrError(`Pool for indy namespace '${namespace}' not found`) } else { @@ -54,7 +63,10 @@ export class IndyVdrPoolService { } } - private async getPoolForLegacyDid(agentContext: AgentContext, did: string): Promise { + private async getPoolForLegacyDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndyVdrPool; nymResponse?: CachedDidResponse['nymResponse'] }> { const pools = this.pools if (pools.length === 0) { @@ -71,7 +83,7 @@ export class IndyVdrPoolService { // If we have the nym response with associated pool in the cache, we'll use that if (cachedNymResponse && pool) { this.logger.trace(`Found ledger id '${pool.indyNamespace}' for did '${did}' in cache`) - return pool + return { pool, nymResponse: cachedNymResponse.nymResponse } } const { successful, rejected } = await this.getSettledDidResponsesFromPools(did, pools) @@ -119,7 +131,7 @@ export class IndyVdrPoolService { }, indyNamespace: value.did.indyNamespace, }) - return value.pool + return { pool: value.pool, nymResponse: value.did.nymResponse } } private async getSettledDidResponsesFromPools(did: string, pools: IndyVdrPool[]) { @@ -159,7 +171,7 @@ export class IndyVdrPoolService { private async getDidFromPool(did: string, pool: IndyVdrPool): Promise { try { this.logger.trace(`Get public did '${did}' from ledger '${pool.indyNamespace}'`) - const request = await new GetNymRequest({ dest: did }) + const request = new GetNymRequest({ dest: did }) this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.indyNamespace}'`) const response = await pool.submitReadRequest(request) diff --git a/packages/indy-vdr/tests/__fixtures__/anoncreds.ts b/packages/indy-vdr/tests/__fixtures__/anoncreds.ts new file mode 100644 index 0000000000..fea36d5fcb --- /dev/null +++ b/packages/indy-vdr/tests/__fixtures__/anoncreds.ts @@ -0,0 +1,30 @@ +export const credentialDefinitionValue = { + primary: { + n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', + s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', + r: { + master_secret: + '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', + age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', + }, + rctxt: + '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', + z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', + }, + revocation: { + g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + g_dash: + '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + htilde: + '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h_cap: + '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + }, +} diff --git a/packages/indy-vdr/tests/helpers.ts b/packages/indy-vdr/tests/helpers.ts index ecaf154ee9..b8a469cd0a 100644 --- a/packages/indy-vdr/tests/helpers.ts +++ b/packages/indy-vdr/tests/helpers.ts @@ -1,7 +1,8 @@ +import type { IndyVdrDidCreateOptions } from '../src/dids/IndyVdrIndyDidRegistrar' import type { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' -import type { AgentContext, Key } from '@aries-framework/core' +import type { Agent } from '@aries-framework/core' -import { KeyType } from '@aries-framework/core' +import { AgentContext, DidDocumentService, Key, KeyType } from '@aries-framework/core' import { AttribRequest, NymRequest } from '@hyperledger/indy-vdr-shared' import { genesisTransactions } from '../../core/tests/helpers' @@ -19,12 +20,26 @@ export const indyVdrModuleConfig = new IndyVdrModuleConfig({ ], }) -export async function createDidOnLedger( - indyVdrPoolService: IndyVdrPoolService, - agentContext: AgentContext, - submitterDid: string, - signerKey: Key -) { +export async function createDidOnLedger(agent: Agent, submitterDid: string) { + const createResult = await agent.dids.create({ + method: 'indy', + options: { + submitterDid: submitterDid, + alias: 'Alias', + role: 'TRUSTEE', + services: [ + new DidDocumentService({ + id: '#', + }), + ], + endpoints: { + endpoint: 'https://agent.com', + types: ['endpoint', 'did-communication', 'DIDComm'], + routingKeys: ['routingKey1', 'routingKey2'], + }, + }, + }) + const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') const key = await agentContext.wallet.createKey({ keyType: KeyType.Ed25519 }) diff --git a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts index 83f3323c54..f3448d169a 100644 --- a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts @@ -9,36 +9,35 @@ import { } from '../../core/tests/helpers' import { IndySdkModule } from '../../indy-sdk/src' import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' -import { IndyVdrSovDidResolver } from '../src' +import { IndyVdrIndyDidResolver, IndyVdrModule, IndyVdrSovDidResolver } from '../src' import { IndyVdrAnonCredsRegistry } from '../src/anoncreds/IndyVdrAnonCredsRegistry' import { IndyVdrPoolService } from '../src/pool' +import { credentialDefinitionValue } from './__fixtures__/anoncreds' import { indyVdrModuleConfig } from './helpers' const agentConfig = getAgentConfig('IndyVdrAnonCredsRegistry') -// TODO: update to module once available -const indyVdrPoolService = new IndyVdrPoolService(agentConfig.logger, indyVdrModuleConfig) -const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') - -// Verkey for the publicDidSeed -const signingKey = Key.fromPublicKeyBase58('FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', KeyType.Ed25519) const indyVdrAnonCredsRegistry = new IndyVdrAnonCredsRegistry() const agent = new Agent({ config: agentConfig, dependencies: agentDependencies, modules: { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + }), indySdk: new IndySdkModule({ indySdk, }), dids: new DidsModule({ - resolvers: [new IndyVdrSovDidResolver()], + resolvers: [new IndyVdrSovDidResolver(), new IndyVdrIndyDidResolver()], }), }, }) -agent.dependencyManager.registerInstance(IndyVdrPoolService, indyVdrPoolService) +const indyVdrPoolService = agent.dependencyManager.resolve(IndyVdrPoolService) +const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') describe('IndyVdrAnonCredsRegistry', () => { beforeAll(async () => { @@ -60,13 +59,18 @@ describe('IndyVdrAnonCredsRegistry', () => { test('register and resolve a schema and credential definition', async () => { const dynamicVersion = `1.${Math.random() * 100}` + const legacyIssuerId = 'TL1EaPFCZ8Si5aUrqScBDt' + const signingKey = Key.fromPublicKeyBase58('FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', KeyType.Ed25519) + const didIndyIssuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + + const legacySchemaId = `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}` + const didIndySchemaId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/SCHEMA/test/${dynamicVersion}` + const schemaResult = await indyVdrAnonCredsRegistry.registerSchema(agent.context, { - options: { - didIndyNamespace: 'pool:localtest', - }, + options: {}, schema: { attrNames: ['age'], - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, name: 'test', version: dynamicVersion, }, @@ -77,31 +81,47 @@ describe('IndyVdrAnonCredsRegistry', () => { state: 'finished', schema: { attrNames: ['age'], - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, name: 'test', version: dynamicVersion, }, - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + schemaId: didIndySchemaId, }, registrationMetadata: {}, schemaMetadata: { indyLedgerSeqNo: expect.any(Number), + }, + }) + + // Wait some time before resolving credential definition object + await new Promise((res) => setTimeout(res, 1000)) + + const legacySchema = await indyVdrAnonCredsRegistry.getSchema(agent.context, legacySchemaId) + expect(legacySchema).toMatchObject({ + schema: { + attrNames: ['age'], + name: 'test', + version: dynamicVersion, + issuerId: legacyIssuerId, + }, + schemaId: legacySchemaId, + resolutionMetadata: {}, + schemaMetadata: { didIndyNamespace: 'pool:localtest', + indyLedgerSeqNo: expect.any(Number), }, }) - const schemaResponse = await indyVdrAnonCredsRegistry.getSchema( - agent.context, - schemaResult.schemaState.schemaId as string - ) - expect(schemaResponse).toMatchObject({ + // Resolve using did indy schema id + const didIndySchema = await indyVdrAnonCredsRegistry.getSchema(agent.context, didIndySchemaId) + expect(didIndySchema).toMatchObject({ schema: { attrNames: ['age'], name: 'test', version: dynamicVersion, - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, }, - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + schemaId: didIndySchemaId, resolutionMetadata: {}, schemaMetadata: { didIndyNamespace: 'pool:localtest', @@ -109,137 +129,72 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }) + const legacyCredentialDefinitionId = `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG` + const didIndyCredentialDefinitionId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG` const credentialDefinitionResult = await indyVdrAnonCredsRegistry.registerCredentialDefinition(agent.context, { credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, tag: 'TAG', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + schemaId: didIndySchemaId, type: 'CL', - value: { - primary: { - n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', - s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', - r: { - master_secret: - '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', - age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', - }, - rctxt: - '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', - z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', - }, - revocation: { - g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - g_dash: - '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - htilde: - '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h_cap: - '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - }, - }, - }, - options: { - didIndyNamespace: 'pool:localtest', + value: credentialDefinitionValue, }, + options: {}, }) expect(credentialDefinitionResult).toMatchObject({ - credentialDefinitionMetadata: { - didIndyNamespace: 'pool:localtest', - }, + credentialDefinitionMetadata: {}, credentialDefinitionState: { credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, tag: 'TAG', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + schemaId: didIndySchemaId, type: 'CL', - value: { - primary: { - n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', - s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', - r: { - master_secret: - '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', - age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', - }, - rctxt: - '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', - z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', - }, - revocation: { - g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - g_dash: - '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - htilde: - '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h_cap: - '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - }, - }, + value: credentialDefinitionValue, }, - credentialDefinitionId: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG`, + credentialDefinitionId: didIndyCredentialDefinitionId, state: 'finished', }, registrationMetadata: {}, }) - const credentialDefinitionResponse = await indyVdrAnonCredsRegistry.getCredentialDefinition( + // Wait some time before resolving credential definition object + await new Promise((res) => setTimeout(res, 1000)) + + const legacyCredentialDefinition = await indyVdrAnonCredsRegistry.getCredentialDefinition( agent.context, - credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId as string + legacyCredentialDefinitionId ) - expect(credentialDefinitionResponse).toMatchObject({ - credentialDefinitionId: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG`, + expect(legacyCredentialDefinition).toMatchObject({ + credentialDefinitionId: legacyCredentialDefinitionId, credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + issuerId: legacyIssuerId, + schemaId: legacySchemaId, tag: 'TAG', type: 'CL', - value: { - primary: { - n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', - s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', - r: { - master_secret: - '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', - age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', - }, - rctxt: - '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', - z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', - }, - revocation: { - g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - g_dash: - '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - htilde: - '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h_cap: - '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - }, - }, + value: credentialDefinitionValue, + }, + credentialDefinitionMetadata: { + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + // resolve using did indy credential definition id + const didIndyCredentialDefinition = await indyVdrAnonCredsRegistry.getCredentialDefinition( + agent.context, + didIndyCredentialDefinitionId + ) + + expect(didIndyCredentialDefinition).toMatchObject({ + credentialDefinitionId: didIndyCredentialDefinitionId, + credentialDefinition: { + issuerId: didIndyIssuerId, + schemaId: didIndySchemaId, + tag: 'TAG', + type: 'CL', + value: credentialDefinitionValue, }, credentialDefinitionMetadata: { didIndyNamespace: 'pool:localtest', @@ -248,12 +203,13 @@ describe('IndyVdrAnonCredsRegistry', () => { }) // We don't support creating a revocation registry using AFJ yet, so we directly use indy-vdr to create the revocation registry - const revocationRegistryDefinitionId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` + const legacyRevocationRegistryId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` + const didIndyRevocationRegistryId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` const revocationRegistryRequest = new RevocationRegistryDefinitionRequest({ submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', revocationRegistryDefinitionV1: { - credDefId: credentialDefinitionResponse.credentialDefinitionId, - id: revocationRegistryDefinitionId, + credDefId: legacyCredentialDefinitionId, + id: legacyRevocationRegistryId, revocDefType: 'CL_ACCUM', tag: 'tag', value: { @@ -277,7 +233,7 @@ describe('IndyVdrAnonCredsRegistry', () => { // Also create a revocation registry entry const revocationEntryRequest = new RevocationRegistryEntryRequest({ - revocationRegistryDefinitionId, + revocationRegistryDefinitionId: legacyRevocationRegistryId, revocationRegistryDefinitionType: 'CL_ACCUM', revocationRegistryEntry: { ver: '1.0', @@ -285,21 +241,50 @@ describe('IndyVdrAnonCredsRegistry', () => { accum: '1', }, }, - submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + submitterDid: legacyIssuerId, }) // After this call we can query the revocation registry entries (using timestamp now) - const response = await pool.submitWriteRequest(agent.context, revocationEntryRequest, signingKey) + const entryResponse = await pool.submitWriteRequest(agent.context, revocationEntryRequest, signingKey) - const revocationRegistryDefintion = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( + const legacyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( agent.context, - revocationRegistryDefinitionId + legacyRevocationRegistryId ) + expect(legacyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinition: { + issuerId: legacyIssuerId, + revocDefType: 'CL_ACCUM', + value: { + maxCredNum: 100, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + }, + tag: 'tag', + credDefId: legacyCredentialDefinitionId, + }, + revocationRegistryDefinitionMetadata: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) - expect(revocationRegistryDefintion).toMatchObject({ - revocationRegistryDefinitionId: `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag`, + const didIndyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( + agent.context, + didIndyRevocationRegistryId + ) + expect(didIndyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: didIndyRevocationRegistryId, revocationRegistryDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, revocDefType: 'CL_ACCUM', value: { maxCredNum: 100, @@ -313,7 +298,7 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }, tag: 'tag', - credDefId: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG`, + credDefId: didIndyCredentialDefinitionId, }, revocationRegistryDefinitionMetadata: { issuanceType: 'ISSUANCE_BY_DEFAULT', @@ -322,26 +307,52 @@ describe('IndyVdrAnonCredsRegistry', () => { resolutionMetadata: {}, }) - const revocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( + const legacyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( + agent.context, + legacyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime + ) + + expect(legacyRevocationStatusList).toMatchObject({ + resolutionMetadata: {}, + revocationStatusList: { + issuerId: legacyIssuerId, + currentAccumulator: '1', + revRegId: legacyRevocationRegistryId, + revocationList: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', + }, + }) + + const didIndyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( agent.context, - revocationRegistryDefinitionId, - response.result.txnMetadata.txnTime + didIndyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime ) - expect(revocationStatusList).toMatchObject({ + expect(didIndyRevocationStatusList).toMatchObject({ resolutionMetadata: {}, revocationStatusList: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, currentAccumulator: '1', - revRegId: `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag`, + revRegId: didIndyRevocationRegistryId, revocationList: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], - timestamp: response.result.txnMetadata.txnTime, + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', }, - revocationStatusListMetadata: { didIndyNamespace: 'pool:localtest' }, }) }) }) diff --git a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts index bc0e5f4ea8..fc456dfa68 100644 --- a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts @@ -1,181 +1,159 @@ -import type { Key } from '@aries-framework/core' - -import { - TypedArrayEncoder, - CacheModuleConfig, - InMemoryLruCache, - JsonTransformer, - KeyType, - SigningProviderRegistry, -} from '@aries-framework/core' +import { DidsModule, Agent, TypedArrayEncoder, JsonTransformer } from '@aries-framework/core' import { parseDid } from '../../core/src/modules/dids/domain/parse' -import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' -import { IndySdkWallet } from '../../indy-sdk/src' +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' -import { IndyVdrSovDidResolver } from '../src/dids' -import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' +import { IndyVdrModule } from '../src' +import { IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrSovDidResolver } from '../src/dids' import { indyDidFromPublicKeyBase58 } from '../src/utils/did' import { createDidOnLedger, indyVdrModuleConfig } from './helpers' -const logger = testLogger -const wallet = new IndySdkWallet(indySdk, logger, new SigningProviderRegistry([])) -const agentConfig = getAgentConfig('IndyVdrResolver E2E', { logger }) - -const cache = new InMemoryLruCache({ limit: 200 }) -const indyVdrSovDidResolver = new IndyVdrSovDidResolver() - -let signerKey: Key - -const agentContext = getAgentContext({ - wallet, - agentConfig, - registerInstances: [ - [IndyVdrPoolService, new IndyVdrPoolService(logger, indyVdrModuleConfig)], - [CacheModuleConfig, new CacheModuleConfig({ cache })], - ], -}) - -const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) +const agent = new Agent( + getAgentOptions( + 'Indy VDR Sov DID resolver', + {}, + { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) describe('indy-vdr DID Resolver E2E', () => { beforeAll(async () => { - await wallet.createAndOpen(agentConfig.walletConfig) - - signerKey = await wallet.createKey({ - privateKey: TypedArrayEncoder.fromString('000000000000000000000000Trustee9'), - keyType: KeyType.Ed25519, - }) + await agent.initialize() }) afterAll(async () => { - for (const pool of indyVdrPoolService.pools) { - pool.close() - } - - await wallet.delete() + await agent.shutdown() + await agent.wallet.delete() }) - describe('did:sov resolver', () => { - test('can resolve a did sov using the pool', async () => { - const did = 'did:sov:TL1EaPFCZ8Si5aUrqScBDt' - const didResult = await indyVdrSovDidResolver.resolve(agentContext, did, parseDid(did)) - expect(JsonTransformer.toJSON(didResult)).toMatchObject({ - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - ], - id: did, - alsoKnownAs: undefined, - controller: undefined, - verificationMethod: [ - { - type: 'Ed25519VerificationKey2018', - controller: did, - id: `${did}#key-1`, - publicKeyBase58: expect.any(String), - }, - { - controller: did, - type: 'X25519KeyAgreementKey2019', - id: `${did}#key-agreement-1`, - publicKeyBase58: expect.any(String), - }, - ], - capabilityDelegation: undefined, - capabilityInvocation: undefined, - authentication: [`${did}#key-1`], - assertionMethod: [`${did}#key-1`], - keyAgreement: [`${did}#key-agreement-1`], - service: undefined, - }, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) + test('resolve a did:sov did', async () => { + const did = 'did:sov:TL1EaPFCZ8Si5aUrqScBDt' + const didResult = await agent.dids.resolve(did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#key-1`, + publicKeyBase58: expect.any(String), + }, + { + controller: did, + type: 'X25519KeyAgreementKey2019', + id: `${did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#key-1`], + assertionMethod: [`${did}#key-1`], + keyAgreement: [`${did}#key-agreement-1`], + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, }) + }) - test('resolve a did with endpoints', async () => { - // First we need to create a new DID and add ATTRIB endpoint to it - const { did } = await createDidOnLedger( - indyVdrPoolService, - agentContext, - indyDidFromPublicKeyBase58(signerKey.publicKeyBase58), - signerKey - ) - - // DID created. Now resolve it - - const fullyQualifiedDid = `did:sov:${did}` - const didResult = await indyVdrSovDidResolver.resolve( - agentContext, - fullyQualifiedDid, - parseDid(fullyQualifiedDid) - ) - expect(JsonTransformer.toJSON(didResult)).toMatchObject({ - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - 'https://didcomm.org/messaging/contexts/v2', - ], - id: fullyQualifiedDid, - alsoKnownAs: undefined, - controller: undefined, - verificationMethod: [ - { - type: 'Ed25519VerificationKey2018', - controller: fullyQualifiedDid, - id: `${fullyQualifiedDid}#key-1`, - publicKeyBase58: expect.any(String), - }, - { - controller: fullyQualifiedDid, - type: 'X25519KeyAgreementKey2019', - id: `${fullyQualifiedDid}#key-agreement-1`, - publicKeyBase58: expect.any(String), - }, - ], - capabilityDelegation: undefined, - capabilityInvocation: undefined, - authentication: [`${fullyQualifiedDid}#key-1`], - assertionMethod: [`${fullyQualifiedDid}#key-1`], - keyAgreement: [`${fullyQualifiedDid}#key-agreement-1`], - service: [ - { - id: `${fullyQualifiedDid}#endpoint`, - type: 'endpoint', - serviceEndpoint: 'https://agent.com', - }, - { - id: `${fullyQualifiedDid}#did-communication`, - type: 'did-communication', - priority: 0, - recipientKeys: [`${fullyQualifiedDid}#key-agreement-1`], - routingKeys: ['routingKey1', 'routingKey2'], - accept: ['didcomm/aip2;env=rfc19'], - serviceEndpoint: 'https://agent.com', - }, - { - id: `${fullyQualifiedDid}#didcomm-1`, - type: 'DIDComm', - serviceEndpoint: 'https://agent.com', - accept: ['didcomm/v2'], - routingKeys: ['routingKey1', 'routingKey2'], - }, - ], - }, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) + test('resolve a did with endpoints', async () => { + await importExistingIndyDidFromPrivateKey(agent, TypedArrayEncoder.fromString('000000000000000000000000Trustee9')) + + // First we need to create a new DID and add ATTRIB endpoint to it + const { did } = await createDidOnLedger( + indyVdrPoolService, + agentContext, + indyDidFromPublicKeyBase58(signerKey.publicKeyBase58), + signerKey + ) + + // DID created. Now resolve it + + const fullyQualifiedDid = `did:sov:${did}` + const didResult = await indyVdrSovDidResolver.resolve(agentContext, fullyQualifiedDid, parseDid(fullyQualifiedDid)) + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: fullyQualifiedDid, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: fullyQualifiedDid, + id: `${fullyQualifiedDid}#key-1`, + publicKeyBase58: expect.any(String), + }, + { + controller: fullyQualifiedDid, + type: 'X25519KeyAgreementKey2019', + id: `${fullyQualifiedDid}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${fullyQualifiedDid}#key-1`], + assertionMethod: [`${fullyQualifiedDid}#key-1`], + keyAgreement: [`${fullyQualifiedDid}#key-agreement-1`], + service: [ + { + id: `${fullyQualifiedDid}#endpoint`, + type: 'endpoint', + serviceEndpoint: 'https://agent.com', + }, + { + id: `${fullyQualifiedDid}#did-communication`, + type: 'did-communication', + priority: 0, + recipientKeys: [`${fullyQualifiedDid}#key-agreement-1`], + routingKeys: ['routingKey1', 'routingKey2'], + accept: ['didcomm/aip2;env=rfc19'], + serviceEndpoint: 'https://agent.com', + }, + { + id: `${fullyQualifiedDid}#didcomm-1`, + type: 'DIDComm', + serviceEndpoint: 'https://agent.com', + accept: ['didcomm/v2'], + routingKeys: ['routingKey1', 'routingKey2'], + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, }) }) }) From 10aca954184b897513af5c2262eac7e97493584d Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 27 Feb 2023 13:23:47 +0100 Subject: [PATCH 04/11] refactor!: set default outbound content type to didcomm v1 (#1314) Signed-off-by: Timo Glastra BREAKING CHANGE: Agent default outbound content type has been changed to DIDComm V1. If you want to use former behaviour, you can do it so by manually setting `didcommMimeType` in `Agent`'s init config: ``` const agent = new Agent({ config: { ... didCommMimeType: DidCommMimeType.V0 }, ... }) ``` --- packages/core/src/agent/AgentConfig.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/agent/AgentConfig.ts b/packages/core/src/agent/AgentConfig.ts index c654ef3eb0..971bff8d6d 100644 --- a/packages/core/src/agent/AgentConfig.ts +++ b/packages/core/src/agent/AgentConfig.ts @@ -62,7 +62,7 @@ export class AgentConfig { } public get didCommMimeType() { - return this.initConfig.didCommMimeType ?? DidCommMimeType.V0 + return this.initConfig.didCommMimeType ?? DidCommMimeType.V1 } /** From 0dba4b99773727804dd096af34f1c8a81f2eb78d Mon Sep 17 00:00:00 2001 From: Ariel Gentile Date: Mon, 27 Feb 2023 15:35:16 -0300 Subject: [PATCH 05/11] fix(anoncreds-rs): save revocation registry index (#1351) Signed-off-by: Ariel Gentile --- packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index b4c1e02f53..f0516665ee 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -251,6 +251,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { schemaName: schema.name, schemaIssuerId: schema.issuerId, schemaVersion: schema.version, + credentialRevocationId: processedCredential.revocationRegistryIndex?.toString(), }) ) From 128a817663dece5fd659f13a8b0075d8b18d08b9 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Tue, 28 Feb 2023 14:59:16 +0100 Subject: [PATCH 06/11] chore: address feedback Signed-off-by: Timo Glastra --- packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts | 4 ++-- packages/indy-sdk/src/anoncreds/utils/identifiers.ts | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts index 90b54b0637..abd3ddd6ed 100644 --- a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -64,8 +64,8 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { const parsed = parseSchemaId(schemaId) - const legaycSchemaId = getLegacySchemaId(parsed.didIdentifier, parsed.schemaName, parsed.schemaVersion) - const indyLedgerSeqNo = getSeqNoFromSchemaId(legaycSchemaId) + const legacySchemaId = getLegacySchemaId(parsed.didIdentifier, parsed.schemaName, parsed.schemaVersion) + const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) if (!schema) { return { diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index fa8b961890..8300a7ea29 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -1,7 +1,5 @@ import { DID_INDY_REGEX } from '../../utils/did' -const legacyIndyIssuerIdRegex = /^[a-zA-Z0-9]{21,22}$/ - const didIndyAnonCredsBase = /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ From c8bb89ea9a9bad54a8e7e57d9b5e22e769fc1dae Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Thu, 2 Mar 2023 14:06:22 +0100 Subject: [PATCH 07/11] fix: did indy support for indy-vdr Signed-off-by: Timo Glastra --- .../anoncreds/tests/legacyAnonCredsSetup.ts | 11 +- packages/core/tests/helpers.ts | 2 +- .../src/dids/IndySdkIndyDidRegistrar.ts | 20 +- .../__tests__/IndySdkIndyDidRegistrar.test.ts | 30 +- .../tests/indy-did-registrar.e2e.test.ts | 6 +- .../src/dids/IndyVdrIndyDidRegistrar.ts | 39 +- .../__tests__/IndyVdrIndyDidRegistrar.test.ts | 706 ++++++++++++++++++ .../__tests__/IndyVdrSovDidResolver.test.ts | 2 +- .../didIndyR1xKJw17sUoXhejEpugMYJ.json | 2 +- .../didIndyWJz9mHyW9BZksioQnRsrAo.json | 7 +- packages/indy-vdr/src/dids/didIndyUtil.ts | 1 + packages/indy-vdr/src/dids/didSovUtil.ts | 13 +- packages/indy-vdr/tests/helpers.ts | 67 +- .../tests/indy-vdr-did-registrar.e2e.test.ts | 136 ++-- .../indy-vdr-indy-did-resolver.e2e.test.ts | 143 ++++ ... => indy-vdr-sov-did-resolver.e2e.test.ts} | 62 +- 16 files changed, 1059 insertions(+), 188 deletions(-) create mode 100644 packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts create mode 100644 packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts rename packages/indy-vdr/tests/{indy-vdr-did-resolver.e2e.test.ts => indy-vdr-sov-did-resolver.e2e.test.ts} (71%) diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 5895e5c075..de9ca6c5ed 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -60,7 +60,13 @@ import { parseSchemaId, } from '../../indy-sdk/src/anoncreds/utils/identifiers' import { getIndySdkModuleConfig } from '../../indy-sdk/tests/setupIndySdkModule' -import { IndyVdrAnonCredsRegistry, IndyVdrSovDidResolver, IndyVdrModule } from '../../indy-vdr/src' +import { + IndyVdrAnonCredsRegistry, + IndyVdrSovDidResolver, + IndyVdrModule, + IndyVdrIndyDidResolver, + IndyVdrIndyDidRegistrar, +} from '../../indy-vdr/src' import { V1CredentialProtocol, V1ProofProtocol, @@ -163,7 +169,8 @@ export const getAskarAnonCredsIndyModules = ({ networks: [indyNetworkConfig], }), dids: new DidsModule({ - resolvers: [new IndyVdrSovDidResolver()], // TODO: Support Registrar for tests + resolvers: [new IndyVdrSovDidResolver(), new IndyVdrIndyDidResolver()], + registrars: [new IndyVdrIndyDidRegistrar()], }), askar: new AskarModule(), cache: new CacheModule({ diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 3309c1afdf..b8c6cb4f2d 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -131,7 +131,7 @@ export async function importExistingIndyDidFromPrivateKey(agent: Agent, privateK const unqualifiedIndyDid = TypedArrayEncoder.toBase58(key.publicKey.slice(0, 16)) // import the did in the wallet so it can be used - await agent.dids.import({ did: `did:sov:${unqualifiedIndyDid}` }) + await agent.dids.import({ did: `did:indy:pool:localtest:${unqualifiedIndyDid}` }) return unqualifiedIndyDid } diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts index 77689dcde6..9c1804f946 100644 --- a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts @@ -199,7 +199,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } } - public async registerPublicDid( + private async registerPublicDid( agentContext: AgentContext, pool: IndySdkPool, unqualifiedSubmitterDid: string, @@ -249,7 +249,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } } - public async setEndpointsForDid( + private async setEndpointsForDid( agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string, @@ -296,9 +296,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } } -export interface IndySdkIndyDidCreateOptions extends DidCreateOptions { - method: 'indy' - did?: string +interface IndySdkIndyDidCreateOptionsBase extends DidCreateOptions { // The indy sdk can only publish a very limited did document (what is mostly known as a legacy did:sov did) and thus we require everything // needed to construct the did document to be passed through the options object. didDocument?: never @@ -313,3 +311,15 @@ export interface IndySdkIndyDidCreateOptions extends DidCreateOptions { privateKey?: Buffer } } + +interface IndySdkIndyDidCreateOptionsWithDid extends IndySdkIndyDidCreateOptionsBase { + method?: never + did: string +} + +interface IndySdkIndyDidCreateOptionsWithoutDid extends IndySdkIndyDidCreateOptionsBase { + method: 'indy' + did?: never +} + +export type IndySdkIndyDidCreateOptions = IndySdkIndyDidCreateOptionsWithDid | IndySdkIndyDidCreateOptionsWithoutDid diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts index 4d4390bd24..b087c499f5 100644 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts @@ -78,7 +78,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if both did and privateKey are provided', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'did:indy:pool1:did-value', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -120,7 +119,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if did is provided, but it is not a valid did:indy did', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'BzCbsNYhMrjHiqZDTUASHg', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -141,7 +139,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if did is provided, but no verkey', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'BzCbsNYhMrjHiqZDTUASHg', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -161,7 +158,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if did and verkey are provided, but the did is not self certifying', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -182,7 +178,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if did is provided, but does not match with the namespace from the submitterDid', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'did:indy:pool2:R1xKJw17sUoXhejEpugMYJ', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -205,7 +200,9 @@ describe('IndySdkIndyDidRegistrar', () => { test('creates a did:indy document without services', async () => { const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) const result = await indySdkIndyDidRegistrar.create(agentContext, { @@ -264,11 +261,12 @@ describe('IndySdkIndyDidRegistrar', () => { }) test('creates a did:indy document by passing did', async () => { - const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', options: { verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', @@ -323,10 +321,14 @@ describe('IndySdkIndyDidRegistrar', () => { test('creates a did:indy document with services', async () => { const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) - const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) const result = await indySdkIndyDidRegistrar.create(agentContext, { @@ -427,10 +429,14 @@ describe('IndySdkIndyDidRegistrar', () => { test('stores the did document', async () => { const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) - const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) const saveCalled = jest.fn() diff --git a/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts index c007eaa561..04781e2e62 100644 --- a/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts @@ -9,12 +9,10 @@ import { legacyIndyDidFromPublicKeyBase58 } from '../src/utils/did' import { getIndySdkModules } from './setupIndySdkModule' const agentOptions = getAgentOptions('Indy Sdk Indy Did Registrar', {}, getIndySdkModules()) +const agent = new Agent(agentOptions) -describe('dids', () => { - let agent: Agent> - +describe('Indy SDK Indy Did Registrar', () => { beforeAll(async () => { - agent = new Agent(agentOptions) await agent.initialize() }) diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts index 241ff8e5dc..8f541fb762 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts @@ -89,7 +89,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { didRegistrationMetadata: {}, didState: { state: 'failed', - reason: `Initial verkey ${options.options.verkey} does not match did ˇ${did}`, + reason: `Initial verkey ${options.options.verkey} does not match did ${did}`, }, } } @@ -119,7 +119,21 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { // Add services if object was passed if (services) { - services.forEach((item) => didDocumentBuilder.addService(item)) + services.forEach((item) => { + const prependDidIfNotPresent = (id: string) => { + return id.startsWith('#') ? `${did}${id}` : id + } + + // Prepend the did to the service id if it is not already there + item.id = prependDidIfNotPresent(item.id) + + // TODO: should we also prepend the did to routingKeys? + if (item instanceof DidCommV1Service) { + item.recipientKeys = item.recipientKeys.map(prependDidIfNotPresent) + } + + didDocumentBuilder.addService(item) + }) const commTypes = [IndyAgentService.type, DidCommV1Service.type, DidCommV2Service.type] const serviceTypes = new Set(services.map((item) => item.type)) @@ -129,6 +143,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { // If there is at least a communication service, add the key agreement key if (commTypes.some((type) => serviceTypes.has(type))) { didDocumentBuilder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') .addVerificationMethod({ controller: did, id: keyAgreementId, @@ -154,9 +169,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { // Build did document const didDocument = didDocumentBuilder.build() - const pool = agentContext.dependencyManager.resolve(IndyVdrPoolService).getPoolForNamespace(submitterNamespace) - // If there are services and we are using legacy indy endpoint attrib, make sure they are suitable before registering the DID if (services && useEndpointAttrib) { const endpoints = endpointsAttribFromServices(services) @@ -284,7 +297,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { response, }) - return unqualifiedDid + return } catch (error) { agentContext.config.logger.error( `Error registering public did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, @@ -345,9 +358,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { } } -export interface IndyVdrDidCreateOptions extends DidCreateOptions { - method: 'indy' - did?: string +interface IndyVdrDidCreateOptionsBase extends DidCreateOptions { didDocument?: never // Not yet supported options: { alias?: string @@ -363,3 +374,15 @@ export interface IndyVdrDidCreateOptions extends DidCreateOptions { privateKey?: Buffer } } + +interface IndyVdrDidCreateOptionsWithDid extends IndyVdrDidCreateOptionsBase { + method?: never + did: string +} + +interface IndyVdrDidCreateOptionsWithoutDid extends IndyVdrDidCreateOptionsBase { + method: 'indy' + did?: never +} + +export type IndyVdrDidCreateOptions = IndyVdrDidCreateOptionsWithDid | IndyVdrDidCreateOptionsWithoutDid diff --git a/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts b/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts new file mode 100644 index 0000000000..e75cc4d97e --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts @@ -0,0 +1,706 @@ +import type { DidRecord, RecordSavedEvent } from '@aries-framework/core' + +import { + DidCommV1Service, + DidCommV2Service, + DidDocumentService, + DidDocument, + DidDocumentRole, + DidRepository, + DidsApi, + EventEmitter, + JsonTransformer, + Key, + KeyType, + RepositoryEventTypes, + SigningProviderRegistry, + TypedArrayEncoder, + VerificationMethod, +} from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { agentDependencies, getAgentConfig, getAgentContext, indySdk, mockProperty } from '../../../../core/tests' +import { IndySdkWallet } from '../../../../indy-sdk/src' +import { IndyVdrPool, IndyVdrPoolService } from '../../pool' +import { IndyVdrIndyDidRegistrar } from '../IndyVdrIndyDidRegistrar' + +jest.mock('../../pool/IndyVdrPool') +const IndyVdrPoolMock = IndyVdrPool as jest.Mock +const poolMock = new IndyVdrPoolMock() +mockProperty(poolMock, 'indyNamespace', 'ns1') + +const agentConfig = getAgentConfig('IndyVdrIndyDidRegistrar') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) + +jest + .spyOn(wallet, 'createKey') + .mockResolvedValue(Key.fromPublicKeyBase58('E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', KeyType.Ed25519)) +const storageService = new InMemoryStorageService() +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const didRepository = new DidRepository(storageService, eventEmitter) + +const agentContext = getAgentContext({ + wallet, + registerInstances: [ + [DidRepository, didRepository], + [IndyVdrPoolService, { getPoolForNamespace: jest.fn().mockReturnValue(poolMock) }], + [ + DidsApi, + { + resolve: jest.fn().mockResolvedValue({ + didDocument: new DidDocument({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + authentication: [ + new VerificationMethod({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }), + ], + }), + }), + }, + ], + ], + agentConfig, +}) + +const indyVdrIndyDidRegistrar = new IndyVdrIndyDidRegistrar() + +describe('IndyVdrIndyDidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('returns an error state if both did and privateKey are provided', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:did-value', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + privateKey: TypedArrayEncoder.fromString('key'), + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Only one of 'seed', 'privateKey' and 'did' must be provided`, + }, + }) + }) + + test('returns an error state if the submitter did is not a valid did:indy did', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but it is not a valid did:indy did', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but no verkey', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'If a did is defined, a matching verkey must be provided', + }, + }) + }) + + test('returns an error state if did and verkey are provided, but the did is not self certifying', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Initial verkey verkey does not match did did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + }, + }) + }) + + test('returns an error state if did is provided, but does not match with the namespace from the submitterDid', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool2:B6xaJg1c2xU3D9ppCtt1CZ', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: + 'The submitter did uses namespace pool1 and the did to register uses namespace pool2. Namespaces must match.', + }, + }) + }) + + test('creates a did:indy document without services', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: { + privateKey, + }, + }) + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'B6xaJg1c2xU3D9ppCtt1CZ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD', + undefined + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('creates a did:indy document by passing did', async () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + options: { + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: {}, + }) + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'B6xaJg1c2xU3D9ppCtt1CZ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD', + undefined + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: {}, + }, + }) + }) + + test('creates a did:indy document with services using diddocContent', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'B6xaJg1c2xU3D9ppCtt1CZ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD', + { + '@context': [], + authentication: [], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + accept: ['didcomm/aip2;env=rfc19'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + { + accept: ['didcomm/v2'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + }, + ], + verificationMethod: [ + { + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + type: 'X25519KeyAgreementKey2019', + }, + ], + } + ) + expect(setEndpointsForDidSpy).not.toHaveBeenCalled() + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('creates a did:indy document with services using attrib', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + useEndpointAttrib: true, + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'B6xaJg1c2xU3D9ppCtt1CZ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(setEndpointsForDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + 'B6xaJg1c2xU3D9ppCtt1CZ', + expect.any(Key), + { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['endpoint', 'did-communication', 'DIDComm'], + } + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('stores the did document', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const saveCalled = jest.fn() + eventEmitter.on>(RepositoryEventTypes.RecordSaved, saveCalled) + + await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(saveCalled).toHaveBeenCalledTimes(1) + const [saveEvent] = saveCalled.mock.calls[0] + + expect(saveEvent.payload.record).toMatchObject({ + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], + }, + didDocument: undefined, + }) + }) + + test('returns an error state when calling update', async () => { + const result = await indyVdrIndyDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:indy not implemented yet`, + }, + }) + }) + + test('returns an error state when calling deactivate', async () => { + const result = await indyVdrIndyDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:indy not implemented yet`, + }, + }) + }) +}) diff --git a/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts b/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts index c8001ccd19..0ed14f5856 100644 --- a/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts +++ b/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts @@ -18,7 +18,7 @@ const agentConfig = getAgentConfig('IndyVdrSovDidResolver') const agentContext = getAgentContext({ agentConfig, - registerInstances: [[IndyVdrPoolService, { getPoolForDid: jest.fn().mockReturnValue(poolMock) }]], + registerInstances: [[IndyVdrPoolService, { getPoolForDid: jest.fn().mockReturnValue({ pool: poolMock }) }]], }) const resolver = new IndyVdrSovDidResolver() diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json index 56014e70be..68874b6fc2 100644 --- a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json @@ -1,5 +1,5 @@ { - "@context": ["https://w3id.org/did/v1"], + "@context": ["https://w3id.org/did/v1", "https://w3id.org/security/suites/ed25519-2018/v1"], "id": "did:indy:ns1:R1xKJw17sUoXhejEpugMYJ", "verificationMethod": [ { diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json index c131549e18..2a58c356ca 100644 --- a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json @@ -1,5 +1,10 @@ { - "@context": ["https://w3id.org/did/v1", "https://didcomm.org/messaging/contexts/v2"], + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1", + "https://didcomm.org/messaging/contexts/v2" + ], "id": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo", "verificationMethod": [ { diff --git a/packages/indy-vdr/src/dids/didIndyUtil.ts b/packages/indy-vdr/src/dids/didIndyUtil.ts index b7c63051d1..c3f8013d81 100644 --- a/packages/indy-vdr/src/dids/didIndyUtil.ts +++ b/packages/indy-vdr/src/dids/didIndyUtil.ts @@ -23,6 +23,7 @@ export function indyDidDocumentFromDid(did: string, verKeyBase58: string) { const publicKeyBase58 = verKeyBase58 const builder = new DidDocumentBuilder(did) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') .addVerificationMethod({ controller: did, id: verificationMethodId, diff --git a/packages/indy-vdr/src/dids/didSovUtil.ts b/packages/indy-vdr/src/dids/didSovUtil.ts index b836eb2eae..0517d00315 100644 --- a/packages/indy-vdr/src/dids/didSovUtil.ts +++ b/packages/indy-vdr/src/dids/didSovUtil.ts @@ -123,14 +123,15 @@ export function endpointsAttribFromServices(services: DidDocumentService[]): Ind const commServiceType = commService.type as CommEndpointType if (types.includes(commServiceType)) { throw new AriesFrameworkError('Only a single communication service per type is supported') - } else { - types.push(commServiceType) } - if (commService instanceof DidCommV1Service || commService instanceof DidCommV2Service) { - if (commService.routingKeys) { - commService.routingKeys.forEach((item) => routingKeys.add(item)) - } + types.push(commServiceType) + + if ( + (commService instanceof DidCommV1Service || commService instanceof DidCommV2Service) && + commService.routingKeys + ) { + commService.routingKeys.forEach((item) => routingKeys.add(item)) } } diff --git a/packages/indy-vdr/tests/helpers.ts b/packages/indy-vdr/tests/helpers.ts index b8a469cd0a..2ea2390329 100644 --- a/packages/indy-vdr/tests/helpers.ts +++ b/packages/indy-vdr/tests/helpers.ts @@ -1,13 +1,10 @@ import type { IndyVdrDidCreateOptions } from '../src/dids/IndyVdrIndyDidRegistrar' -import type { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import type { Agent } from '@aries-framework/core' -import { AgentContext, DidDocumentService, Key, KeyType } from '@aries-framework/core' -import { AttribRequest, NymRequest } from '@hyperledger/indy-vdr-shared' +import { DidCommV1Service, DidCommV2Service, DidDocumentService, KeyType } from '@aries-framework/core' import { genesisTransactions } from '../../core/tests/helpers' import { IndyVdrModuleConfig } from '../src/IndyVdrModuleConfig' -import { indyDidFromPublicKeyBase58 } from '../src/utils/did' export const indyVdrModuleConfig = new IndyVdrModuleConfig({ networks: [ @@ -21,51 +18,45 @@ export const indyVdrModuleConfig = new IndyVdrModuleConfig({ }) export async function createDidOnLedger(agent: Agent, submitterDid: string) { + const key = await agent.wallet.createKey({ keyType: KeyType.Ed25519 }) + const createResult = await agent.dids.create({ method: 'indy', options: { - submitterDid: submitterDid, + submitterDid, alias: 'Alias', role: 'TRUSTEE', + verkey: key.publicKeyBase58, + useEndpointAttrib: true, services: [ new DidDocumentService({ - id: '#', + id: `#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', }), ], - endpoints: { - endpoint: 'https://agent.com', - types: ['endpoint', 'did-communication', 'DIDComm'], - routingKeys: ['routingKey1', 'routingKey2'], - }, }, }) - const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') - - const key = await agentContext.wallet.createKey({ keyType: KeyType.Ed25519 }) - const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) - - const nymRequest = new NymRequest({ - dest: did, - submitterDid, - verkey: key.publicKeyBase58, - }) - - await pool.submitWriteRequest(agentContext, nymRequest, signerKey) - - const attribRequest = new AttribRequest({ - submitterDid: did, - targetDid: did, - raw: JSON.stringify({ - endpoint: { - endpoint: 'https://agent.com', - types: ['endpoint', 'did-communication', 'DIDComm'], - routingKeys: ['routingKey1', 'routingKey2'], - }, - }), - }) - - await pool.submitWriteRequest(agentContext, attribRequest, key) + if (!createResult.didState.did) { + throw new Error( + `Did was not created. ${createResult.didState.state === 'failed' ? createResult.didState.reason : 'Not finished'}` + ) + } - return { did, key } + return { did: createResult.didState.did, key } } diff --git a/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts index ec6724d576..65eee36cf3 100644 --- a/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts @@ -1,94 +1,81 @@ +import type { IndyVdrDidCreateOptions } from '../src/dids/IndyVdrIndyDidRegistrar' + import { Key, - InjectionSymbols, - CacheModuleConfig, - InMemoryLruCache, JsonTransformer, KeyType, - SigningProviderRegistry, TypedArrayEncoder, DidCommV1Service, DidCommV2Service, DidDocumentService, + Agent, + DidsModule, } from '@aries-framework/core' import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' -import { Subject } from 'rxjs' -import { InMemoryStorageService } from '../../../tests/InMemoryStorageService' -import { agentDependencies, getAgentConfig, getAgentContext } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' -import { IndySdkWallet } from '../../indy-sdk/src' +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrModule, IndyVdrSovDidResolver } from '../src' import { IndyVdrIndyDidRegistrar } from '../src/dids/IndyVdrIndyDidRegistrar' import { IndyVdrIndyDidResolver } from '../src/dids/IndyVdrIndyDidResolver' import { indyDidFromNamespaceAndInitialKey } from '../src/dids/didIndyUtil' -import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import { DID_INDY_REGEX } from '../src/utils/did' import { indyVdrModuleConfig } from './helpers' -const logger = testLogger -const wallet = new IndySdkWallet(indySdk, logger, new SigningProviderRegistry([])) - -const agentConfig = getAgentConfig('IndyVdrIndyDidRegistrar E2E', { logger }) - -const cache = new InMemoryLruCache({ limit: 200 }) -const indyVdrIndyDidResolver = new IndyVdrIndyDidResolver() -const indyVdrIndyDidRegistrar = new IndyVdrIndyDidRegistrar() - -let signerKey: Key - -const agentContext = getAgentContext({ - wallet, - agentConfig, - registerInstances: [ - [InjectionSymbols.Stop$, new Subject()], - [InjectionSymbols.AgentDependencies, agentDependencies], - [InjectionSymbols.StorageService, new InMemoryStorageService()], - [IndyVdrPoolService, new IndyVdrPoolService(logger, indyVdrModuleConfig)], - [CacheModuleConfig, new CacheModuleConfig({ cache })], - ], -}) +const agent = new Agent( + getAgentOptions( + 'Indy VDR Indy DID Registrar', + {}, + { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) -const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) +describe('Indy VDR Indy Did Registrar', () => { + let submitterDid: string -describe('Indy VDR registrar E2E', () => { beforeAll(async () => { - await wallet.createAndOpen(agentConfig.walletConfig) - - signerKey = await wallet.createKey({ - privateKey: TypedArrayEncoder.fromString('000000000000000000000000Trustee9'), - keyType: KeyType.Ed25519, - }) + await agent.initialize() + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString('000000000000000000000000Trustee9') + ) + submitterDid = `did:indy:pool:localtest:${unqualifiedSubmitterDid}` }) afterAll(async () => { - for (const pool of indyVdrPoolService.pools) { - pool.close() - } - - await wallet.delete() + await agent.shutdown() + await agent.wallet.delete() }) test('can register a did:indy without services', async () => { - const didRegistrationResult = await indyVdrIndyDidRegistrar.create(agentContext, { + const didRegistrationResult = await agent.dids.create({ method: 'indy', options: { - submitterDid: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', - submitterVerkey: signerKey.publicKeyBase58, + submitterDid, }, }) expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ didDocumentMetadata: {}, - didRegistrationMetadata: { - didIndyNamespace: 'pool:localtest', - }, + didRegistrationMetadata: {}, didState: { state: 'finished', did: expect.stringMatching(DID_INDY_REGEX), didDocument: { - '@context': ['https://w3id.org/did/v1'], + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: expect.stringMatching(DID_INDY_REGEX), alsoKnownAs: undefined, controller: undefined, @@ -109,14 +96,12 @@ describe('Indy VDR registrar E2E', () => { }) const did = didRegistrationResult.didState.did - if (!did) { - throw Error('did not defined') - } + if (!did) throw Error('did not defined') - const didResolutionResult = await indyVdrIndyDidResolver.resolve(agentContext, did) + const didResolutionResult = await agent.dids.resolve(did) expect(JsonTransformer.toJSON(didResolutionResult)).toMatchObject({ didDocument: { - '@context': ['https://w3id.org/did/v1'], + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: did, alsoKnownAs: undefined, controller: undefined, @@ -154,26 +139,22 @@ describe('Indy VDR registrar E2E', () => { 'pool:localtest', Key.fromPublicKey(keyPair.publicKey, KeyType.Ed25519) ) - const didRegistrationResult = await indyVdrIndyDidRegistrar.create(agentContext, { - method: 'indy', + const didRegistrationResult = await agent.dids.create({ did, options: { - submitterDid: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', - submitterVerkey: signerKey.publicKeyBase58, + submitterDid, verkey, }, }) expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ didDocumentMetadata: {}, - didRegistrationMetadata: { - didIndyNamespace: 'pool:localtest', - }, + didRegistrationMetadata: {}, didState: { state: 'finished', did, didDocument: { - '@context': ['https://w3id.org/did/v1'], + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: did, alsoKnownAs: undefined, controller: undefined, @@ -193,10 +174,10 @@ describe('Indy VDR registrar E2E', () => { }, }) - const didResult = await indyVdrIndyDidResolver.resolve(agentContext, did) + const didResult = await agent.dids.resolve(did) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ didDocument: { - '@context': ['https://w3id.org/did/v1'], + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: did, alsoKnownAs: undefined, controller: undefined, @@ -229,7 +210,7 @@ describe('Indy VDR registrar E2E', () => { .slice(0, 32) ) - const key = await wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) + const key = await agent.wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(key.publicKey)) const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(key.publicKey) @@ -238,12 +219,10 @@ describe('Indy VDR registrar E2E', () => { Key.fromPublicKey(key.publicKey, KeyType.Ed25519) ) - const didRegistrationResult = await indyVdrIndyDidRegistrar.create(agentContext, { - method: 'indy', + const didRegistrationResult = await agent.dids.create({ did, options: { - submitterDid: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', - submitterVerkey: signerKey.publicKeyBase58, + submitterDid, useEndpointAttrib: true, verkey, services: [ @@ -271,7 +250,12 @@ describe('Indy VDR registrar E2E', () => { }) const expectedDidDocument = { - '@context': ['https://w3id.org/did/v1', 'https://didcomm.org/messaging/contexts/v2'], + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], id: did, alsoKnownAs: undefined, controller: undefined, @@ -319,9 +303,7 @@ describe('Indy VDR registrar E2E', () => { expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ didDocumentMetadata: {}, - didRegistrationMetadata: { - didIndyNamespace: 'pool:localtest', - }, + didRegistrationMetadata: {}, didState: { state: 'finished', did, @@ -329,7 +311,7 @@ describe('Indy VDR registrar E2E', () => { }, }) - const didResult = await indyVdrIndyDidResolver.resolve(agentContext, did) + const didResult = await agent.dids.resolve(did) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ didDocument: expectedDidDocument, didDocumentMetadata: {}, diff --git a/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..209b05e698 --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts @@ -0,0 +1,143 @@ +import { DidsModule, Agent, TypedArrayEncoder, JsonTransformer } from '@aries-framework/core' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrModule } from '../src' +import { IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrSovDidResolver } from '../src/dids' + +import { createDidOnLedger, indyVdrModuleConfig } from './helpers' + +const agent = new Agent( + getAgentOptions( + 'Indy VDR Indy DID resolver', + {}, + { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) + +describe('indy-vdr DID Resolver E2E', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + test('resolve a did:indy did', async () => { + const did = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + const didResult = await agent.dids.resolve(did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + assertionMethod: undefined, + keyAgreement: undefined, + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('resolve a did with endpoints', async () => { + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString('000000000000000000000000Trustee9') + ) + + // First we need to create a new DID and add ATTRIB endpoint to it + const { did } = await createDidOnLedger(agent, `did:indy:pool:localtest:${unqualifiedSubmitterDid}`) + + // DID created. Now resolve it + const didResult = await agent.dids.resolve(did) + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: expect.any(String), + }, + { + controller: did, + type: 'X25519KeyAgreementKey2019', + id: `${did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + assertionMethod: undefined, + keyAgreement: [`${did}#key-agreement-1`], + service: [ + { + id: `${did}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${did}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${did}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + { + id: `${did}#didcomm-1`, + accept: ['didcomm/v2'], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'DIDComm', + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts similarity index 71% rename from packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts rename to packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts index fc456dfa68..d025bc6fe8 100644 --- a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts @@ -1,12 +1,11 @@ import { DidsModule, Agent, TypedArrayEncoder, JsonTransformer } from '@aries-framework/core' -import { parseDid } from '../../core/src/modules/dids/domain/parse' import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' import { IndySdkModule } from '../../indy-sdk/src' import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' import { IndyVdrModule } from '../src' import { IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrSovDidResolver } from '../src/dids' -import { indyDidFromPublicKeyBase58 } from '../src/utils/did' +import { parseIndyDid } from '../src/dids/didIndyUtil' import { createDidOnLedger, indyVdrModuleConfig } from './helpers' @@ -29,7 +28,7 @@ const agent = new Agent( ) ) -describe('indy-vdr DID Resolver E2E', () => { +describe('Indy VDR Sov DID Resolver', () => { beforeAll(async () => { await agent.initialize() }) @@ -82,20 +81,19 @@ describe('indy-vdr DID Resolver E2E', () => { }) test('resolve a did with endpoints', async () => { - await importExistingIndyDidFromPrivateKey(agent, TypedArrayEncoder.fromString('000000000000000000000000Trustee9')) + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString('000000000000000000000000Trustee9') + ) // First we need to create a new DID and add ATTRIB endpoint to it - const { did } = await createDidOnLedger( - indyVdrPoolService, - agentContext, - indyDidFromPublicKeyBase58(signerKey.publicKeyBase58), - signerKey - ) + const { did } = await createDidOnLedger(agent, `did:indy:pool:localtest:${unqualifiedSubmitterDid}`) + const { id: unqualifiedDid } = parseIndyDid(did) + const sovDid = `did:sov:${unqualifiedDid}` // DID created. Now resolve it + const didResult = await agent.dids.resolve(sovDid) - const fullyQualifiedDid = `did:sov:${did}` - const didResult = await indyVdrSovDidResolver.resolve(agentContext, fullyQualifiedDid, parseDid(fullyQualifiedDid)) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ didDocument: { '@context': [ @@ -104,49 +102,49 @@ describe('indy-vdr DID Resolver E2E', () => { 'https://w3id.org/security/suites/x25519-2019/v1', 'https://didcomm.org/messaging/contexts/v2', ], - id: fullyQualifiedDid, + id: sovDid, alsoKnownAs: undefined, controller: undefined, verificationMethod: [ { type: 'Ed25519VerificationKey2018', - controller: fullyQualifiedDid, - id: `${fullyQualifiedDid}#key-1`, + controller: sovDid, + id: `${sovDid}#key-1`, publicKeyBase58: expect.any(String), }, { - controller: fullyQualifiedDid, + controller: sovDid, type: 'X25519KeyAgreementKey2019', - id: `${fullyQualifiedDid}#key-agreement-1`, + id: `${sovDid}#key-agreement-1`, publicKeyBase58: expect.any(String), }, ], capabilityDelegation: undefined, capabilityInvocation: undefined, - authentication: [`${fullyQualifiedDid}#key-1`], - assertionMethod: [`${fullyQualifiedDid}#key-1`], - keyAgreement: [`${fullyQualifiedDid}#key-agreement-1`], + authentication: [`${sovDid}#key-1`], + assertionMethod: [`${sovDid}#key-1`], + keyAgreement: [`${sovDid}#key-agreement-1`], service: [ { - id: `${fullyQualifiedDid}#endpoint`, + id: `${sovDid}#endpoint`, + serviceEndpoint: 'http://localhost:3000', type: 'endpoint', - serviceEndpoint: 'https://agent.com', }, { - id: `${fullyQualifiedDid}#did-communication`, - type: 'did-communication', - priority: 0, - recipientKeys: [`${fullyQualifiedDid}#key-agreement-1`], - routingKeys: ['routingKey1', 'routingKey2'], + id: `${sovDid}#did-communication`, accept: ['didcomm/aip2;env=rfc19'], - serviceEndpoint: 'https://agent.com', + priority: 0, + recipientKeys: [`${sovDid}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', }, { - id: `${fullyQualifiedDid}#didcomm-1`, - type: 'DIDComm', - serviceEndpoint: 'https://agent.com', + id: `${sovDid}#didcomm-1`, accept: ['didcomm/v2'], - routingKeys: ['routingKey1', 'routingKey2'], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'DIDComm', }, ], }, From d39c2c209be5e28f366abf26957b15f6909d8284 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Fri, 3 Mar 2023 17:47:23 +0100 Subject: [PATCH 08/11] style: eslint fixes Signed-off-by: Timo Glastra --- packages/indy-vdr/src/pool/IndyVdrPoolService.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts index f648c2a49b..d8e31f72a7 100644 --- a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts +++ b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts @@ -3,8 +3,6 @@ import type { GetNymResponse } from '@hyperledger/indy-vdr-shared' import { Logger, InjectionSymbols, injectable, inject, CacheModuleConfig } from '@aries-framework/core' import { GetNymRequest } from '@hyperledger/indy-vdr-shared' -import { request, response } from 'express' -import { async } from 'rxjs' import { IndyVdrModuleConfig } from '../IndyVdrModuleConfig' import { IndyVdrError, IndyVdrNotFoundError, IndyVdrNotConfiguredError } from '../error' From 864db32b62da4954cf479a0535a026cbe631a004 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Mon, 6 Mar 2023 16:13:52 +0100 Subject: [PATCH 09/11] namespace identifier Signed-off-by: Timo Glastra --- .../legacy-indy-format-services.test.ts | 4 +- .../tests/InMemoryAnonCredsRegistry.ts | 25 ++++++---- .../anoncreds/tests/legacyAnonCredsSetup.ts | 4 +- .../services/IndySdkAnonCredsRegistry.ts | 48 +++++++++++-------- .../services/IndySdkIssuerService.ts | 10 ++-- .../utils/__tests__/identifiers.test.ts | 12 ++--- .../src/anoncreds/utils/identifiers.ts | 26 +++++----- .../src/dids/IndySdkIndyDidRegistrar.ts | 22 +++++---- .../src/dids/IndySdkIndyDidResolver.ts | 6 +-- packages/indy-sdk/src/dids/didIndyUtil.ts | 4 +- .../tests/sov-did-resolver.e2e.test.ts | 4 +- .../src/anoncreds/IndyVdrAnonCredsRegistry.ts | 48 +++++++++++-------- .../utils/_tests_/identifiers.test.ts | 12 ++--- .../src/anoncreds/utils/identifiers.ts | 26 +++++----- .../src/dids/IndyVdrIndyDidRegistrar.ts | 23 ++++----- .../src/dids/IndyVdrIndyDidResolver.ts | 8 ++-- packages/indy-vdr/src/dids/didIndyUtil.ts | 4 +- .../indy-vdr-sov-did-resolver.e2e.test.ts | 4 +- 18 files changed, 159 insertions(+), 131 deletions(-) diff --git a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts index 4606899650..af5686ad5d 100644 --- a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts +++ b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts @@ -163,10 +163,10 @@ describe('Legacy indy format services', () => { ] const cd = parseCredentialDefinitionId(credentialDefinitionState.credentialDefinitionId) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.didIdentifier, cd.schemaSeqNo, cd.tag) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.namespaceIdentifier, cd.schemaSeqNo, cd.tag) const s = parseSchemaId(schemaState.schemaId) - const legacySchemaId = getLegacySchemaId(s.didIdentifier, s.schemaName, s.schemaVersion) + const legacySchemaId = getLegacySchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) // Holder creates proposal holderCredentialRecord.credentialAttributes = credentialAttributes diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts index abd3ddd6ed..5c2fc9954a 100644 --- a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -64,7 +64,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { const parsed = parseSchemaId(schemaId) - const legacySchemaId = getLegacySchemaId(parsed.didIdentifier, parsed.schemaName, parsed.schemaVersion) + const legacySchemaId = getLegacySchemaId(parsed.namespaceIdentifier, parsed.schemaName, parsed.schemaVersion) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) if (!schema) { @@ -94,16 +94,21 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { agentContext: AgentContext, options: RegisterSchemaOptions ): Promise { - const { id: didIdentifier, namespace } = parseIndyDid(options.schema.issuerId) - const didIndySchemaId = getDidIndySchemaId(namespace, didIdentifier, options.schema.name, options.schema.version) - const legacySchemaId = getLegacySchemaId(didIdentifier, options.schema.name, options.schema.version) + const { namespaceIdentifier, namespace } = parseIndyDid(options.schema.issuerId) + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, options.schema.name, options.schema.version) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) this.schemas[didIndySchemaId] = options.schema this.schemas[legacySchemaId] = { ...options.schema, - issuerId: didIdentifier, + issuerId: namespaceIdentifier, } return { @@ -152,22 +157,22 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { ): Promise { const parsedSchema = parseSchemaId(options.credentialDefinition.schemaId) const legacySchemaId = getLegacySchemaId( - parsedSchema.didIdentifier, + parsedSchema.namespaceIdentifier, parsedSchema.schemaName, parsedSchema.schemaVersion ) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) - const { id: didIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( namespace, - didIdentifier, + namespaceIdentifier, indyLedgerSeqNo, options.credentialDefinition.tag ) const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( - didIdentifier, + namespaceIdentifier, indyLedgerSeqNo, options.credentialDefinition.tag ) @@ -175,7 +180,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { this.credentialDefinitions[didIndyCredentialDefinitionId] = options.credentialDefinition this.credentialDefinitions[legacyCredentialDefinitionId] = { ...options.credentialDefinition, - issuerId: didIdentifier, + issuerId: namespaceIdentifier, schemaId: legacySchemaId, } diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index de9ca6c5ed..d0e40a33a6 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -481,8 +481,8 @@ export async function prepareForAnonCredsIssuance(agent: Agent, { attributeNames const s = parseSchemaId(schema.schemaId) const cd = parseCredentialDefinitionId(credentialDefinition.credentialDefinitionId) - const legacySchemaId = getLegacySchemaId(s.didIdentifier, s.schemaName, s.schemaVersion) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.didIdentifier, cd.schemaSeqNo, cd.tag) + const legacySchemaId = getLegacySchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.namespaceIdentifier, cd.schemaSeqNo, cd.tag) // Wait some time pass to let ledger settle the object await sleep(1000) diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index 8cf73ee38c..8c222b1a18 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -45,12 +45,12 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) // parse schema id (supports did:indy and legacy) - const { did, didIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) + const { did, namespaceIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`) // even though we support did:indy and legacy identifiers we always need to fetch using the legacy identifier - const legacySchemaId = getLegacySchemaId(didIdentifier, schemaName, schemaVersion) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, schemaName, schemaVersion) const request = await indySdk.buildGetSchemaRequest(null, legacySchemaId) agentContext.config.logger.trace( @@ -107,7 +107,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { try { // This will throw an error if trying to register a schema with a legacy indy identifier. We only support did:indy identifiers // for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. - const { id: unqualifiedDid, namespace } = parseIndyDid(options.schema.issuerId) + const { namespaceIdentifier, namespace } = parseIndyDid(options.schema.issuerId) const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) @@ -118,8 +118,13 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { options.schema ) - const didIndySchemaId = getDidIndySchemaId(namespace, unqualifiedDid, options.schema.name, options.schema.version) - const legacySchemaId = getLegacySchemaId(unqualifiedDid, options.schema.name, options.schema.version) + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, options.schema.name, options.schema.version) const schema = { attrNames: options.schema.attrNames, @@ -131,7 +136,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { // buildSchemaRequest (seqNo is not yet known) } as IndySdkSchema - const request = await indySdk.buildSchemaRequest(unqualifiedDid, schema) + const request = await indySdk.buildSchemaRequest(namespaceIdentifier, schema) const submitterKey = await verificationKeyForIndyDid(agentContext, options.schema.issuerId) const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterKey) @@ -186,14 +191,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) // we support did:indy and legacy identifiers - const { did, didIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) + const { did, namespaceIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve credential definition '${credentialDefinitionId}'` ) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, tag) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) const request = await indySdk.buildGetCredDefRequest(null, legacyCredentialDefinitionId) agentContext.config.logger.trace( @@ -221,7 +226,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { // Format the schema id based on the type of the credential definition id const schemaId = credentialDefinitionId.startsWith('did:indy') - ? getDidIndySchemaId(pool.didIndyNamespace, didIdentifier, schema.name, schema.version) + ? getDidIndySchemaId(pool.didIndyNamespace, namespaceIdentifier, schema.name, schema.version) : schema.schemaId return { @@ -276,7 +281,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { try { // This will throw an error if trying to register a credential defintion with a legacy indy identifier. We only support did:indy // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. - const { id: unqualifiedDid, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) @@ -309,18 +314,18 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( - unqualifiedDid, + namespaceIdentifier, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag ) const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( namespace, - unqualifiedDid, + namespaceIdentifier, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag ) - const request = await indySdk.buildCredDefRequest(unqualifiedDid, { + const request = await indySdk.buildCredDefRequest(namespaceIdentifier, { id: legacyCredentialDefinitionId, // Indy ledger requires the credential schemaId to be a string of the schema seqNo. schemaId: schemaMetadata.indyLedgerSeqNo.toString(), @@ -372,7 +377,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const { did, didIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = + const { did, namespaceIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = parseRevocationRegistryId(revocationRegistryDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) @@ -381,7 +386,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ) const legacyRevocationRegistryId = getLegacyRevocationRegistryId( - didIdentifier, + namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag @@ -409,8 +414,13 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ) const credentialDefinitionId = revocationRegistryDefinitionId.startsWith('did:indy:') - ? getDidIndyCredentialDefinitionId(pool.didIndyNamespace, didIdentifier, schemaSeqNo, credentialDefinitionTag) - : getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, credentialDefinitionTag) + ? getDidIndyCredentialDefinitionId( + pool.didIndyNamespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag + ) + : getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) return { resolutionMetadata: {}, @@ -461,7 +471,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const { did, didIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = parseRevocationRegistryId(revocationRegistryId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) @@ -470,7 +480,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ) const legacyRevocationRegistryId = getLegacyRevocationRegistryId( - didIdentifier, + namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts index 2b5365522b..72abbbdea8 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts @@ -33,13 +33,13 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { public async createSchema(agentContext: AgentContext, options: CreateSchemaOptions): Promise { // We only support passing qualified did:indy issuer ids in the indy issuer service when creating objects - const { id: unqualifiedDid } = parseIndyDid(options.issuerId) + const { namespaceIdentifier } = parseIndyDid(options.issuerId) const { name, version, attrNames, issuerId } = options assertIndySdkWallet(agentContext.wallet) try { - const [, schema] = await this.indySdk.issuerCreateSchema(unqualifiedDid, name, version, attrNames) + const [, schema] = await this.indySdk.issuerCreateSchema(namespaceIdentifier, name, version, attrNames) return { issuerId, @@ -60,10 +60,10 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { const { tag, supportRevocation, schema, issuerId, schemaId } = options // We only support passing qualified did:indy issuer ids in the indy issuer service when creating objects - const { id: unqualifiedDid } = parseIndyDid(options.issuerId) + const { namespaceIdentifier } = parseIndyDid(options.issuerId) // parse schema in a way that supports both unqualified and qualified identifiers - const legacySchemaId = getLegacySchemaId(unqualifiedDid, schema.name, schema.version) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, schema.name, schema.version) if (!metadata) throw new AriesFrameworkError('The metadata parameter is required when using Indy, but received undefined.') @@ -72,7 +72,7 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { assertIndySdkWallet(agentContext.wallet) const [, credentialDefinition] = await this.indySdk.issuerCreateAndStoreCredentialDef( agentContext.wallet.handle, - unqualifiedDid, + namespaceIdentifier, indySdkSchemaFromAnonCreds(legacySchemaId, schema, metadata.indyLedgerSchemaSeqNo), tag, 'CL', diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts index ca1751c4e2..74488d4108 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -114,7 +114,7 @@ describe('identifiers', () => { test('parses legacy schema id', () => { expect(parseSchemaId('SDqTzbVuCowusqGBNbNDjH:2:schema-name:1.0')).toEqual({ did: 'SDqTzbVuCowusqGBNbNDjH', - didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', schemaName: 'schema-name', schemaVersion: '1.0', }) @@ -123,7 +123,7 @@ describe('identifiers', () => { test('parses did:indy schema id', () => { expect(parseSchemaId('did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0')).toEqual( { - didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', did: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', schemaName: 'schema-name', schemaVersion: '1.0', @@ -137,7 +137,7 @@ describe('identifiers', () => { test('parses legacy credential definition id', () => { expect(parseCredentialDefinitionId('TL1EaPFCZ8Si5aUrqScBDt:3:CL:10:TAG')).toEqual({ did: 'TL1EaPFCZ8Si5aUrqScBDt', - didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', schemaSeqNo: '10', tag: 'TAG', }) @@ -147,7 +147,7 @@ describe('identifiers', () => { expect( parseCredentialDefinitionId('did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/10/TAG') ).toEqual({ - didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', did: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', namespace: 'pool:localtest', schemaSeqNo: '10', @@ -162,7 +162,7 @@ describe('identifiers', () => { parseRevocationRegistryId('5nDyJVP1NrcPAttP3xwMB9:4:5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npdb:CL_ACCUM:TAG1') ).toEqual({ did: '5nDyJVP1NrcPAttP3xwMB9', - didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', schemaSeqNo: '56495', credentialDefinitionTag: 'npdb', revocationRegistryTag: 'TAG1', @@ -174,7 +174,7 @@ describe('identifiers', () => { parseRevocationRegistryId('did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1') ).toEqual({ namespace: 'sovrin', - didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', did: 'did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9', schemaSeqNo: '56495', credentialDefinitionTag: 'npdb', diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index 6a847f321c..19f1df864c 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -6,34 +6,34 @@ import { DID_INDY_REGEX } from '../../utils/did' const didIndyAnonCredsBase = - /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ + /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ -// did:indy::/anoncreds/v0/SCHEMA// +// did:indy::/anoncreds/v0/SCHEMA// const didIndySchemaIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/SCHEMA/(?.+)/(?[0-9.]+)$` ) -// :2:: +// :2:: const legacyIndySchemaIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ -// did:indy::/anoncreds/v0/CLAIM_DEF// +// did:indy::/anoncreds/v0/CLAIM_DEF// const didIndyCredentialDefinitionIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/CLAIM_DEF/(?[1-9][0-9]*)/(?.+)$` ) -// :3:CL:: +// :3:CL:: const legacyIndyCredentialDefinitionIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ -// did:indy::/anoncreds/v0/REV_REG_DEF/// +// did:indy::/anoncreds/v0/REV_REG_DEF/// const didIndyRevocationRegistryIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/REV_REG_DEF/(?[1-9][0-9]*)/(?.+)/(?.+)$` ) -// :4::3:CL::CL_ACCUM: +// :4::3:CL::CL_ACCUM: const legacyIndyRevocationRegistryIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ // combines both legacy and did:indy anoncreds identifiers and also the issuer id const indySdkAnonCredsRegexes = [ @@ -104,7 +104,7 @@ export function getDidIndyRevocationRegistryId( interface ParsedSchemaId { did: string - didIdentifier: string + namespaceIdentifier: string schemaName: string schemaVersion: string namespace?: string @@ -120,7 +120,7 @@ export function parseSchemaId(schemaId: string) { interface ParsedCredentialDefinitionId { did: string - didIdentifier: string + namespaceIdentifier: string schemaSeqNo: string tag: string namespace?: string @@ -138,7 +138,7 @@ export function parseCredentialDefinitionId(credentialDefinitionId: string) { interface ParsedRevocationRegistryId { did: string - didIdentifier: string + namespaceIdentifier: string schemaSeqNo: string credentialDefinitionTag: string revocationRegistryTag: string diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts index 9c1804f946..a7aba8eab1 100644 --- a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts @@ -33,7 +33,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { const { alias, role, submitterDid, endpoints } = options.options let did = options.did - let didIdentifier: string + let namespaceIdentifier: string let verificationKey: Key const privateKey = options.secret?.privateKey @@ -52,7 +52,8 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { assertIndySdkWallet(agentContext.wallet) // Parse submitterDid and extract namespace based on the submitter did - const { namespace: submitterNamespace, id: submitterDidIdentifier } = parseIndyDid(submitterDid) + const { namespace: submitterNamespace, namespaceIdentifier: submitterNamespaceIdentifier } = + parseIndyDid(submitterDid) const submitterSigningKey = await verificationKeyForIndyDid(agentContext, submitterDid) // Only supports version 1 did identifier (which is same as did:sov) @@ -68,11 +69,12 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } } - const { namespace, id } = parseIndyDid(did) - didIdentifier = id + const { namespace, namespaceIdentifier: _namespaceIdentifier } = parseIndyDid(did) + namespaceIdentifier = _namespaceIdentifier + verificationKey = Key.fromPublicKeyBase58(options.options.verkey, KeyType.Ed25519) - if (!isLegacySelfCertifiedDid(didIdentifier, options.options.verkey)) { + if (!isLegacySelfCertifiedDid(namespaceIdentifier, options.options.verkey)) { return { didDocumentMetadata: {}, didRegistrationMetadata: {}, @@ -96,17 +98,17 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } else { // Create a new key and calculate did according to the rules for indy did method verificationKey = await agentContext.wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) - didIdentifier = legacyIndyDidFromPublicKeyBase58(verificationKey.publicKeyBase58) - did = `did:indy:${submitterNamespace}:${didIdentifier}` + namespaceIdentifier = legacyIndyDidFromPublicKeyBase58(verificationKey.publicKeyBase58) + did = `did:indy:${submitterNamespace}:${namespaceIdentifier}` } const pool = indySdkPoolService.getPoolForNamespace(submitterNamespace) await this.registerPublicDid( agentContext, pool, - submitterDidIdentifier, + submitterNamespaceIdentifier, submitterSigningKey, - didIdentifier, + namespaceIdentifier, verificationKey, alias, role @@ -119,7 +121,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { if (endpoints) { const keyAgreementId = `${did}#key-agreement-1` - await this.setEndpointsForDid(agentContext, pool, didIdentifier, verificationKey, endpoints) + await this.setEndpointsForDid(agentContext, pool, namespaceIdentifier, verificationKey, endpoints) didDocumentBuilder .addContext('https://w3id.org/security/suites/x25519-2019/v1') diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts index 836ed8040b..4aa0ddf1d3 100644 --- a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts @@ -18,13 +18,13 @@ export class IndySdkIndyDidResolver implements DidResolver { const didDocumentMetadata = {} try { - const { id: unqualifiedDid, namespace } = parseIndyDid(did) + const { namespaceIdentifier, namespace } = parseIndyDid(did) const poolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const pool = poolService.getPoolForNamespace(namespace) - const nym = await this.getPublicDid(agentContext, pool, unqualifiedDid) - const endpoints = await this.getEndpointsForDid(agentContext, pool, unqualifiedDid) + const nym = await this.getPublicDid(agentContext, pool, namespaceIdentifier) + const endpoints = await this.getEndpointsForDid(agentContext, pool, namespaceIdentifier) // For modern did:indy DIDs, we assume that GET_NYM is always a full verkey in base58. // For backwards compatibility, we accept a shortened verkey and convert it using previous convention diff --git a/packages/indy-sdk/src/dids/didIndyUtil.ts b/packages/indy-sdk/src/dids/didIndyUtil.ts index af8adacf77..928ae1007e 100644 --- a/packages/indy-sdk/src/dids/didIndyUtil.ts +++ b/packages/indy-sdk/src/dids/didIndyUtil.ts @@ -14,8 +14,8 @@ import { DID_INDY_REGEX } from '../utils/did' export function parseIndyDid(did: string) { const match = did.match(DID_INDY_REGEX) if (match) { - const [, namespace, id] = match - return { namespace, id } + const [, namespace, namespaceIdentifier] = match + return { namespace, namespaceIdentifier } } else { throw new AriesFrameworkError(`${did} is not a valid did:indy did`) } diff --git a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts index 714faac7ca..fd33a35696 100644 --- a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -43,8 +43,8 @@ describe('Indy SDK Sov DID resolver', () => { if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') - const { id: unqualifiedDid } = parseIndyDid(createResult.didState.did) - const sovDid = `did:sov:${unqualifiedDid}` + const { namespaceIdentifier } = parseIndyDid(createResult.didState.did) + const sovDid = `did:sov:${namespaceIdentifier}` const didResult = await agent.dids.resolve(sovDid) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ diff --git a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts index f185eb3f2e..ca2c1149f0 100644 --- a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts +++ b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts @@ -46,12 +46,12 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) // parse schema id (supports did:indy and legacy) - const { did, didIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) + const { did, namespaceIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.indyNamespace}'`) // even though we support did:indy and legacy identifiers we always need to fetch using the legacy identifier - const legacySchemaId = getLegacySchemaId(didIdentifier, schemaName, schemaVersion) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, schemaName, schemaVersion) const request = new GetSchemaRequest({ schemaId: legacySchemaId }) agentContext.config.logger.trace( @@ -115,7 +115,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { try { // This will throw an error if trying to register a schema with a legacy indy identifier. We only support did:indy identifiers // for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. - const { id: unqualifiedDid, namespace } = parseIndyDid(options.schema.issuerId) + const { namespaceIdentifier, namespace } = parseIndyDid(options.schema.issuerId) const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) @@ -125,11 +125,16 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { options.schema ) - const didIndySchemaId = getDidIndySchemaId(namespace, unqualifiedDid, options.schema.name, options.schema.version) - const legacySchemaId = getLegacySchemaId(unqualifiedDid, options.schema.name, options.schema.version) + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, options.schema.name, options.schema.version) const schemaRequest = new SchemaRequest({ - submitterDid: unqualifiedDid, + submitterDid: namespaceIdentifier, schema: { id: legacySchemaId, name: options.schema.name, @@ -191,14 +196,14 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) // we support did:indy and legacy identifiers - const { did, didIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) + const { did, namespaceIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Getting credential definition '${credentialDefinitionId}' from ledger '${pool.indyNamespace}'` ) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, tag) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) const request = new GetCredentialDefinitionRequest({ credentialDefinitionId: legacyCredentialDefinitionId, }) @@ -209,7 +214,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const response = await pool.submitReadRequest(request) // We need to fetch the schema to determine the schemaId (we only have the seqNo) - const schema = await this.fetchIndySchemaWithSeqNo(agentContext, response.result.ref, didIdentifier) + const schema = await this.fetchIndySchemaWithSeqNo(agentContext, response.result.ref, namespaceIdentifier) if (!schema || !response.result.data) { agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`) @@ -226,7 +231,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { // Format the schema id based on the type of the credential definition id const schemaId = credentialDefinitionId.startsWith('did:indy') - ? getDidIndySchemaId(pool.indyNamespace, didIdentifier, schema.schema.name, schema.schema.version) + ? getDidIndySchemaId(pool.indyNamespace, namespaceIdentifier, schema.schema.name, schema.schema.version) : schema.schema.schemaId return { @@ -267,7 +272,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { try { // This will throw an error if trying to register a credential defintion with a legacy indy identifier. We only support did:indy // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. - const { id: unqualifiedDid, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) @@ -304,13 +309,13 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ) const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( namespace, - unqualifiedDid, + namespaceIdentifier, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag ) const credentialDefinitionRequest = new CredentialDefinitionRequest({ - submitterDid: unqualifiedDid, + submitterDid: namespaceIdentifier, credentialDefinition: { ver: '1.0', id: legacyCredentialDefinitionId, @@ -369,7 +374,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { try { const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const { did, didIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = + const { did, namespaceIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = parseRevocationRegistryId(revocationRegistryDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) @@ -378,7 +383,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ) const legacyRevocationRegistryId = getLegacyRevocationRegistryId( - didIdentifier, + namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag @@ -418,8 +423,13 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ) const credentialDefinitionId = revocationRegistryDefinitionId.startsWith('did:indy:') - ? getDidIndyCredentialDefinitionId(pool.indyNamespace, didIdentifier, schemaSeqNo, credentialDefinitionTag) - : getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, credentialDefinitionTag) + ? getDidIndyCredentialDefinitionId( + pool.indyNamespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag + ) + : getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) const revocationRegistryDefinition = { issuerId: did, @@ -475,7 +485,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { try { const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const { did, didIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = parseRevocationRegistryId(revocationRegistryId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) @@ -484,7 +494,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ) const legacyRevocationRegistryId = getLegacyRevocationRegistryId( - didIdentifier, + namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag diff --git a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts index 19ff274a29..555605a1d9 100644 --- a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts +++ b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts @@ -114,7 +114,7 @@ describe('identifiers', () => { test('parses legacy schema id', () => { expect(parseSchemaId('SDqTzbVuCowusqGBNbNDjH:2:schema-name:1.0')).toEqual({ did: 'SDqTzbVuCowusqGBNbNDjH', - didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', schemaName: 'schema-name', schemaVersion: '1.0', }) @@ -123,7 +123,7 @@ describe('identifiers', () => { test('parses did:indy schema id', () => { expect(parseSchemaId('did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0')).toEqual( { - didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', did: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', schemaName: 'schema-name', schemaVersion: '1.0', @@ -137,7 +137,7 @@ describe('identifiers', () => { test('parses legacy credential definition id', () => { expect(parseCredentialDefinitionId('TL1EaPFCZ8Si5aUrqScBDt:3:CL:10:TAG')).toEqual({ did: 'TL1EaPFCZ8Si5aUrqScBDt', - didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', schemaSeqNo: '10', tag: 'TAG', }) @@ -147,7 +147,7 @@ describe('identifiers', () => { expect( parseCredentialDefinitionId('did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/10/TAG') ).toEqual({ - didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', did: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', namespace: 'pool:localtest', schemaSeqNo: '10', @@ -162,7 +162,7 @@ describe('identifiers', () => { parseRevocationRegistryId('5nDyJVP1NrcPAttP3xwMB9:4:5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npdb:CL_ACCUM:TAG1') ).toEqual({ did: '5nDyJVP1NrcPAttP3xwMB9', - didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', schemaSeqNo: '56495', credentialDefinitionTag: 'npdb', revocationRegistryTag: 'TAG1', @@ -174,7 +174,7 @@ describe('identifiers', () => { parseRevocationRegistryId('did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1') ).toEqual({ namespace: 'sovrin', - didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', did: 'did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9', schemaSeqNo: '56495', credentialDefinitionTag: 'npdb', diff --git a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts index 337ad73d2b..e7e1a2bd49 100644 --- a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts @@ -6,34 +6,34 @@ import { DID_INDY_REGEX } from '../../utils/did' const didIndyAnonCredsBase = - /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ + /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ -// did:indy::/anoncreds/v0/SCHEMA// +// did:indy::/anoncreds/v0/SCHEMA// const didIndySchemaIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/SCHEMA/(?.+)/(?[0-9.]+)$` ) -// :2:: +// :2:: const legacyIndySchemaIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ -// did:indy::/anoncreds/v0/CLAIM_DEF// +// did:indy::/anoncreds/v0/CLAIM_DEF// const didIndyCredentialDefinitionIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/CLAIM_DEF/(?[1-9][0-9]*)/(?.+)$` ) -// :3:CL:: +// :3:CL:: const legacyIndyCredentialDefinitionIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ -// did:indy::/anoncreds/v0/REV_REG_DEF/// +// did:indy::/anoncreds/v0/REV_REG_DEF/// const didIndyRevocationRegistryIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/REV_REG_DEF/(?[1-9][0-9]*)/(?.+)/(?.+)$` ) -// :4::3:CL::CL_ACCUM: +// :4::3:CL::CL_ACCUM: const legacyIndyRevocationRegistryIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ // combines both legacy and did:indy anoncreds identifiers and also the issuer id const indyVdrAnonCredsRegexes = [ @@ -104,7 +104,7 @@ export function getDidIndyRevocationRegistryId( interface ParsedSchemaId { did: string - didIdentifier: string + namespaceIdentifier: string schemaName: string schemaVersion: string namespace?: string @@ -120,7 +120,7 @@ export function parseSchemaId(schemaId: string) { interface ParsedCredentialDefinitionId { did: string - didIdentifier: string + namespaceIdentifier: string schemaSeqNo: string tag: string namespace?: string @@ -138,7 +138,7 @@ export function parseCredentialDefinitionId(credentialDefinitionId: string) { interface ParsedRevocationRegistryId { did: string - didIdentifier: string + namespaceIdentifier: string schemaSeqNo: string credentialDefinitionTag: string revocationRegistryTag: string diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts index 8f541fb762..d32e8947ec 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts @@ -47,7 +47,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { const { alias, role, submitterDid, services, useEndpointAttrib } = options.options let did = options.did - let didIdentifier: string + let namespaceIdentifier: string let verificationKey: Key const allowOne = [privateKey, seed, did].filter((e) => e !== undefined) @@ -64,7 +64,8 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { try { // Parse submitterDid and extract namespace based on the submitter did - const { namespace: submitterNamespace, id: submitterDidIdentifier } = parseIndyDid(submitterDid) + const { namespace: submitterNamespace, namespaceIdentifier: submitterNamespaceIdentifier } = + parseIndyDid(submitterDid) const submitterSigningKey = await verificationKeyForIndyDid(agentContext, submitterDid) if (did) { @@ -79,8 +80,8 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { } } - const { namespace, id } = parseIndyDid(did) - didIdentifier = id + const { namespace, namespaceIdentifier: _namespaceIdentifier } = parseIndyDid(did) + namespaceIdentifier = _namespaceIdentifier verificationKey = Key.fromPublicKeyBase58(options.options.verkey, KeyType.Ed25519) if (!isSelfCertifiedIndyDid(did, options.options.verkey)) { @@ -109,8 +110,8 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { verificationKey = await agentContext.wallet.createKey({ privateKey, seed, keyType: KeyType.Ed25519 }) const buffer = Hasher.hash(verificationKey.publicKey, 'sha2-256') - didIdentifier = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) - did = `did:indy:${submitterNamespace}:${didIdentifier}` + namespaceIdentifier = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + did = `did:indy:${submitterNamespace}:${namespaceIdentifier}` } // Create base did document @@ -176,21 +177,21 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { await this.registerPublicDid( agentContext, pool, - submitterDidIdentifier, + submitterNamespaceIdentifier, submitterSigningKey, - didIdentifier, + namespaceIdentifier, verificationKey, alias, role ) - await this.setEndpointsForDid(agentContext, pool, didIdentifier, verificationKey, endpoints) + await this.setEndpointsForDid(agentContext, pool, namespaceIdentifier, verificationKey, endpoints) } else { await this.registerPublicDid( agentContext, pool, - submitterDidIdentifier, + submitterNamespaceIdentifier, submitterSigningKey, - didIdentifier, + namespaceIdentifier, verificationKey, alias, role, diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts index 02369f6fca..124e5da88e 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts @@ -37,25 +37,25 @@ export class IndyVdrIndyDidResolver implements DidResolver { } private async buildDidDocument(agentContext: AgentContext, did: string) { - const { id: unqualifiedDid, namespace } = parseIndyDid(did) + const { namespaceIdentifier, namespace } = parseIndyDid(did) const poolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) const pool = poolService.getPoolForNamespace(namespace) - const nym = await this.getPublicDid(pool, unqualifiedDid) + const nym = await this.getPublicDid(pool, namespaceIdentifier) // Create base Did Document // For modern did:indy DIDs, we assume that GET_NYM is always a full verkey in base58. // For backwards compatibility, we accept a shortened verkey and convert it using previous convention - const verkey = getFullVerkey(unqualifiedDid, nym.verkey) + const verkey = getFullVerkey(namespaceIdentifier, nym.verkey) const builder = indyDidDocumentFromDid(did, verkey) // If GET_NYM does not return any diddocContent, fallback to legacy GET_ATTRIB endpoint if (!nym.diddocContent) { const keyAgreementId = `${did}#key-agreement-1` - const endpoints = await this.getEndpointsForDid(agentContext, pool, unqualifiedDid) + const endpoints = await this.getEndpointsForDid(agentContext, pool, namespaceIdentifier) if (endpoints) { builder diff --git a/packages/indy-vdr/src/dids/didIndyUtil.ts b/packages/indy-vdr/src/dids/didIndyUtil.ts index 9546cee180..e91f5ebd2b 100644 --- a/packages/indy-vdr/src/dids/didIndyUtil.ts +++ b/packages/indy-vdr/src/dids/didIndyUtil.ts @@ -42,8 +42,8 @@ export function createKeyAgreementKey(verkey: string) { export function parseIndyDid(did: string) { const match = did.match(DID_INDY_REGEX) if (match) { - const [, namespace, id] = match - return { namespace, id } + const [, namespace, namespaceIdentifier] = match + return { namespace, namespaceIdentifier } } else { throw new AriesFrameworkError(`${did} is not a valid did:indy did`) } diff --git a/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts index d025bc6fe8..2ff501c641 100644 --- a/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts @@ -88,8 +88,8 @@ describe('Indy VDR Sov DID Resolver', () => { // First we need to create a new DID and add ATTRIB endpoint to it const { did } = await createDidOnLedger(agent, `did:indy:pool:localtest:${unqualifiedSubmitterDid}`) - const { id: unqualifiedDid } = parseIndyDid(did) - const sovDid = `did:sov:${unqualifiedDid}` + const { namespaceIdentifier } = parseIndyDid(did) + const sovDid = `did:sov:${namespaceIdentifier}` // DID created. Now resolve it const didResult = await agent.dids.resolve(sovDid) From 399367df1d8ea92544d4c20c2234d27e6feed183 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 8 Mar 2023 12:57:28 +0100 Subject: [PATCH 10/11] first write to prevent timing issue Signed-off-by: Timo Glastra --- .../indy-sdk-anoncreds-registry.e2e.test.ts | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts index 5d54c04d13..12a7cd6848 100644 --- a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts @@ -211,6 +211,27 @@ describe('IndySdkAnonCredsRegistry', () => { await indySdkPoolService.submitWriteRequest(agent.context, pool, revocationRegistryRequest, signingKey) + // indySdk.buildRevRegEntry panics, so we just pass a custom request directly + const entryResponse = await indySdkPoolService.submitWriteRequest( + agent.context, + pool, + { + identifier: legacyIssuerId, + operation: { + revocDefType: 'CL_ACCUM', + revocRegDefId: legacyRevocationRegistryId, + type: '114', + value: { + accum: + '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', + }, + }, + protocolVersion: 2, + reqId: Math.floor(Math.random() * 1000000), + }, + signingKey + ) + const legacyRevocationRegistryDefinition = await indySdkAnonCredsRegistry.getRevocationRegistryDefinition( agent.context, legacyRevocationRegistryId @@ -271,27 +292,6 @@ describe('IndySdkAnonCredsRegistry', () => { resolutionMetadata: {}, }) - // indySdk.buildRevRegEntry panics, so we just pass a custom request directly - const entryResponse = await indySdkPoolService.submitWriteRequest( - agent.context, - pool, - { - identifier: legacyIssuerId, - operation: { - revocDefType: 'CL_ACCUM', - revocRegDefId: legacyRevocationRegistryId, - type: '114', - value: { - accum: - '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', - }, - }, - protocolVersion: 2, - reqId: Math.floor(Math.random() * 1000000), - }, - signingKey - ) - const legacyRevocationStatusList = await indySdkAnonCredsRegistry.getRevocationStatusList( agent.context, legacyRevocationRegistryId, From 294de2c1101b6bb9e60b0855aa17ab1f3f3c8385 Mon Sep 17 00:00:00 2001 From: Timo Glastra Date: Wed, 8 Mar 2023 13:06:19 +0100 Subject: [PATCH 11/11] chore: update indy-vdr Signed-off-by: Timo Glastra --- packages/indy-vdr/package.json | 4 ++-- yarn.lock | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/indy-vdr/package.json b/packages/indy-vdr/package.json index 088c8da018..98b6d946f5 100644 --- a/packages/indy-vdr/package.json +++ b/packages/indy-vdr/package.json @@ -26,10 +26,10 @@ "dependencies": { "@aries-framework/anoncreds": "0.3.3", "@aries-framework/core": "0.3.3", - "@hyperledger/indy-vdr-shared": "^0.1.0-dev.6" + "@hyperledger/indy-vdr-shared": "^0.1.0-dev.10" }, "devDependencies": { - "@hyperledger/indy-vdr-nodejs": "^0.1.0-dev.6", + "@hyperledger/indy-vdr-nodejs": "^0.1.0-dev.10", "@stablelib/ed25519": "^1.0.2", "rimraf": "^4.0.7", "rxjs": "^7.2.0", diff --git a/yarn.lock b/yarn.lock index a96c0c26f9..ae76e3b622 100644 --- a/yarn.lock +++ b/yarn.lock @@ -896,22 +896,22 @@ dependencies: fast-text-encoding "^1.0.3" -"@hyperledger/indy-vdr-nodejs@^0.1.0-dev.6": - version "0.1.0-dev.6" - resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.1.0-dev.6.tgz#28946107feb6c641839de843cc7ddca9eef01f8d" - integrity sha512-jtFRkfjiveKIfeyTx9qDAUXLtpFaiZlUGN2ts2zX8QvY6XGZEKc6LvhMPzLW5kLW34u6ldEqjG4mjyAawUKRsA== +"@hyperledger/indy-vdr-nodejs@^0.1.0-dev.10": + version "0.1.0-dev.10" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.1.0-dev.10.tgz#f5fc988b3c11766475f7f084f814af8a4eca0caf" + integrity sha512-wETVax9HRidhRTRiRqwLTj/looNCvjZ3vdqMoqmPbmet5sM9eRkU9v4ipz4paqz4LiZipTx2fisLyYskKW9g6A== dependencies: - "@hyperledger/indy-vdr-shared" "0.1.0-dev.6" + "@hyperledger/indy-vdr-shared" "0.1.0-dev.10" "@mapbox/node-pre-gyp" "^1.0.10" ffi-napi "^4.0.3" ref-array-di "^1.2.2" ref-napi "^3.0.3" ref-struct-di "^1.1.1" -"@hyperledger/indy-vdr-shared@0.1.0-dev.6", "@hyperledger/indy-vdr-shared@^0.1.0-dev.6": - version "0.1.0-dev.6" - resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.1.0-dev.6.tgz#345b6ff60d29d74615ee882e225af674315a0bda" - integrity sha512-MIUdm3zIwKfFZmZSwbAPiZHEZE0HhsIPjeEWiu//Z1m9GDDSNhEyxsHuVN17pE0pcxwbuCQrIaK3v4Tc6x0jJw== +"@hyperledger/indy-vdr-shared@0.1.0-dev.10", "@hyperledger/indy-vdr-shared@^0.1.0-dev.10": + version "0.1.0-dev.10" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.1.0-dev.10.tgz#2a6d09a3a6ae3458ae9209ab7b6f09435e88b9c9" + integrity sha512-7fr9/Iliu0u94qE8u1g/RoLJOwMH0kFdclttwBHfTAKTQyfoXUPe1yIZ8ITRgLwnmFYPQpRa54lU+uv15c37fg== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0"