diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index ea0c64d337..20b9c5a74d 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -32,7 +32,7 @@ import { AnonCredsCredentialRepository, AnonCredsLinkSecretRepository, AnonCredsRestrictionWrapper, - legacyIndyCredentialDefinitionIdRegex, + unqualifiedCredentialDefinitionIdRegex, AnonCredsRegistryService, } from '@aries-framework/anoncreds' import { AriesFrameworkError, JsonTransformer, TypedArrayEncoder, injectable, utils } from '@aries-framework/core' @@ -213,7 +213,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { throw new AnonCredsRsError('Link Secret value not stored') } - const isLegacyIdentifier = credentialOffer.cred_def_id.match(legacyIndyCredentialDefinitionIdRegex) + const isLegacyIdentifier = credentialOffer.cred_def_id.match(unqualifiedCredentialDefinitionIdRegex) if (!isLegacyIdentifier && useLegacyProverDid) { throw new AriesFrameworkError('Cannot use legacy prover_did with non-legacy identifiers') } diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts index 91c439d4b1..383c6e94e7 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts @@ -15,6 +15,10 @@ import type { AgentContext } from '@aries-framework/core' import type { CredentialDefinitionPrivate, JsonObject, KeyCorrectnessProof } from '@hyperledger/anoncreds-shared' import { + parseIndyDid, + getUnqualifiedSchemaId, + parseIndySchemaId, + isUnqualifiedCredentialDefinitionId, AnonCredsKeyCorrectnessProofRepository, AnonCredsCredentialDefinitionPrivateRepository, AnonCredsCredentialDefinitionRepository, @@ -87,22 +91,35 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { let credentialOffer: CredentialOffer | undefined try { + // The getByCredentialDefinitionId supports both qualified and unqualified identifiers, even though the + // record is always stored using the qualified identifier. const credentialDefinitionRecord = await agentContext.dependencyManager .resolve(AnonCredsCredentialDefinitionRepository) .getByCredentialDefinitionId(agentContext, options.credentialDefinitionId) + // We fetch the keyCorrectnessProof based on the credential definition record id, as the + // credential definition id passed to this module could be unqualified, and the key correctness + // proof is only stored using the qualified identifier. const keyCorrectnessProofRecord = await agentContext.dependencyManager .resolve(AnonCredsKeyCorrectnessProofRepository) - .getByCredentialDefinitionId(agentContext, options.credentialDefinitionId) + .getByCredentialDefinitionId(agentContext, credentialDefinitionRecord.credentialDefinitionId) if (!credentialDefinitionRecord) { throw new AnonCredsRsError(`Credential Definition ${credentialDefinitionId} not found`) } + let schemaId = credentialDefinitionRecord.credentialDefinition.schemaId + + // if the credentialDefinitionId is not qualified, we need to transform the schemaId to also be unqualified + if (isUnqualifiedCredentialDefinitionId(options.credentialDefinitionId)) { + const { namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(schemaId) + schemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) + } + credentialOffer = CredentialOffer.create({ credentialDefinitionId, keyCorrectnessProof: keyCorrectnessProofRecord?.value, - schemaId: credentialDefinitionRecord.credentialDefinition.schemaId, + schemaId, }) return credentialOffer.toJson() as unknown as AnonCredsCredentialOffer @@ -135,9 +152,25 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { .resolve(AnonCredsCredentialDefinitionRepository) .getByCredentialDefinitionId(agentContext, options.credentialRequest.cred_def_id) + // We fetch the private record based on the cred def id from the cred def record, as the + // credential definition id passed to this module could be unqualified, and the private record + // is only stored using the qualified identifier. const credentialDefinitionPrivateRecord = await agentContext.dependencyManager .resolve(AnonCredsCredentialDefinitionPrivateRepository) - .getByCredentialDefinitionId(agentContext, options.credentialRequest.cred_def_id) + .getByCredentialDefinitionId(agentContext, credentialDefinitionRecord.credentialDefinitionId) + + let credentialDefinition = credentialDefinitionRecord.credentialDefinition + + if (isUnqualifiedCredentialDefinitionId(options.credentialRequest.cred_def_id)) { + const { namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(credentialDefinition.schemaId) + const { namespaceIdentifier: unqualifiedDid } = parseIndyDid(credentialDefinition.issuerId) + parseIndyDid + credentialDefinition = { + ...credentialDefinition, + schemaId: getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion), + issuerId: unqualifiedDid, + } + } credential = Credential.create({ credentialDefinition: credentialDefinitionRecord.credentialDefinition as unknown as JsonObject, diff --git a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts index 49b73ed7f3..1b5d3db10d 100644 --- a/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts +++ b/packages/anoncreds-rs/src/services/__tests__/AnonCredsRsServices.test.ts @@ -1,6 +1,10 @@ import type { AnonCredsProofRequest } from '@aries-framework/anoncreds' import { + getUnqualifiedSchemaId, + parseIndySchemaId, + getUnqualifiedCredentialDefinitionId, + parseIndyCredentialDefinitionId, AnonCredsModuleConfig, AnonCredsHolderServiceSymbol, AnonCredsIssuerServiceSymbol, @@ -236,4 +240,212 @@ describeRunInNodeVersion([18], 'AnonCredsRsServices', () => { expect(verifiedProof).toBeTruthy() }) + + test('issuance flow with unqualified identifiers', async () => { + // Use qualified identifiers to create schema and credential definition (we only support qualified identifiers for these) + const issuerId = 'did:indy:pool:localtest:A4CYPASJYRZRt98YWrac3H' + + const schema = await anonCredsIssuerService.createSchema(agentContext, { + attrNames: ['name', 'age'], + issuerId, + name: 'Employee Credential', + version: '1.0.0', + }) + + const { schemaState } = await registry.registerSchema(agentContext, { + schema, + options: {}, + }) + + const { credentialDefinition, credentialDefinitionPrivate, keyCorrectnessProof } = + await anonCredsIssuerService.createCredentialDefinition(agentContext, { + issuerId, + schemaId: schemaState.schemaId as string, + schema, + tag: 'Employee Credential', + supportRevocation: false, + }) + + const { credentialDefinitionState } = await registry.registerCredentialDefinition(agentContext, { + credentialDefinition, + options: {}, + }) + + if ( + !credentialDefinitionState.credentialDefinition || + !credentialDefinitionState.credentialDefinitionId || + !schemaState.schema || + !schemaState.schemaId + ) { + throw new Error('Failed to create schema or credential definition') + } + + if (!credentialDefinitionPrivate || !keyCorrectnessProof) { + throw new Error('Failed to get private part of credential definition') + } + + await agentContext.dependencyManager.resolve(AnonCredsSchemaRepository).save( + agentContext, + new AnonCredsSchemaRecord({ + schema: schemaState.schema, + schemaId: schemaState.schemaId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionRepository).save( + agentContext, + new AnonCredsCredentialDefinitionRecord({ + credentialDefinition: credentialDefinitionState.credentialDefinition, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + methodName: 'inMemory', + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsCredentialDefinitionPrivateRepository).save( + agentContext, + new AnonCredsCredentialDefinitionPrivateRecord({ + value: credentialDefinitionPrivate, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + await agentContext.dependencyManager.resolve(AnonCredsKeyCorrectnessProofRepository).save( + agentContext, + new AnonCredsKeyCorrectnessProofRecord({ + value: keyCorrectnessProof, + credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + }) + ) + + const { namespaceIdentifier, schemaSeqNo, tag } = parseIndyCredentialDefinitionId( + credentialDefinitionState.credentialDefinitionId + ) + const unqualifiedCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + namespaceIdentifier, + schemaSeqNo, + tag + ) + + const parsedSchema = parseIndySchemaId(schemaState.schemaId) + const unqualifiedSchemaId = getUnqualifiedSchemaId( + parsedSchema.namespaceIdentifier, + parsedSchema.schemaName, + parsedSchema.schemaVersion + ) + + // Create offer with unqualified credential definition id + const credentialOffer = await anonCredsIssuerService.createCredentialOffer(agentContext, { + credentialDefinitionId: unqualifiedCredentialDefinitionId, + }) + + const linkSecret = await anonCredsHolderService.createLinkSecret(agentContext, { linkSecretId: 'someLinkSecretId' }) + expect(linkSecret.linkSecretId).toBe('someLinkSecretId') + + await agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository).save( + agentContext, + new AnonCredsLinkSecretRecord({ + value: linkSecret.linkSecretValue, + linkSecretId: linkSecret.linkSecretId, + }) + ) + + const unqualifiedCredentialDefinition = await registry.getCredentialDefinition( + agentContext, + credentialOffer.cred_def_id + ) + const unqualifiedSchema = await registry.getSchema(agentContext, credentialOffer.schema_id) + if (!unqualifiedCredentialDefinition.credentialDefinition || !unqualifiedSchema.schema) { + throw new Error('unable to fetch credential definition or schema') + } + + const credentialRequestState = await anonCredsHolderService.createCredentialRequest(agentContext, { + credentialDefinition: unqualifiedCredentialDefinition.credentialDefinition, + credentialOffer, + linkSecretId: linkSecret.linkSecretId, + }) + + const { credential } = await anonCredsIssuerService.createCredential(agentContext, { + credentialOffer, + credentialRequest: credentialRequestState.credentialRequest, + credentialValues: { + name: { raw: 'John', encoded: encodeCredentialValue('John') }, + age: { raw: '25', encoded: encodeCredentialValue('25') }, + }, + }) + + const credentialId = 'holderCredentialId2' + + const storedId = await anonCredsHolderService.storeCredential(agentContext, { + credential, + credentialDefinition: unqualifiedCredentialDefinition.credentialDefinition, + schema: unqualifiedSchema.schema, + credentialDefinitionId: credentialOffer.cred_def_id, + credentialRequestMetadata: credentialRequestState.credentialRequestMetadata, + credentialId, + }) + + expect(storedId).toEqual(credentialId) + + const credentialInfo = await anonCredsHolderService.getCredential(agentContext, { + credentialId, + }) + + expect(credentialInfo).toEqual({ + credentialId, + attributes: { + age: '25', + name: 'John', + }, + schemaId: unqualifiedSchemaId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, + revocationRegistryId: null, + credentialRevocationId: undefined, // Should it be null in this case? + methodName: 'inMemory', + }) + + const proofRequest: AnonCredsProofRequest = { + nonce: anoncreds.generateNonce(), + name: 'pres_req_1', + version: '0.1', + requested_attributes: { + attr1_referent: { + name: 'name', + }, + attr2_referent: { + name: 'age', + }, + }, + requested_predicates: { + predicate1_referent: { name: 'age', p_type: '>=' as const, p_value: 18 }, + }, + } + + const proof = await anonCredsHolderService.createProof(agentContext, { + credentialDefinitions: { [unqualifiedCredentialDefinitionId]: credentialDefinition }, + proofRequest, + selectedCredentials: { + attributes: { + attr1_referent: { credentialId, credentialInfo, revealed: true }, + attr2_referent: { credentialId, credentialInfo, revealed: true }, + }, + predicates: { + predicate1_referent: { credentialId, credentialInfo }, + }, + selfAttestedAttributes: {}, + }, + schemas: { [unqualifiedSchemaId]: schema }, + revocationRegistries: {}, + }) + + const verifiedProof = await anonCredsVerifierService.verifyProof(agentContext, { + credentialDefinitions: { [unqualifiedCredentialDefinitionId]: credentialDefinition }, + proof, + proofRequest, + schemas: { [unqualifiedSchemaId]: schema }, + revocationRegistries: {}, + }) + + expect(verifiedProof).toBeTruthy() + }) }) diff --git a/packages/anoncreds-rs/tests/anoncreds-flow.test.ts b/packages/anoncreds-rs/tests/anoncreds-flow.test.ts index 0014f217e3..b3465a543d 100644 --- a/packages/anoncreds-rs/tests/anoncreds-flow.test.ts +++ b/packages/anoncreds-rs/tests/anoncreds-flow.test.ts @@ -38,7 +38,7 @@ import { AnonCredsRsHolderService } from '../src/services/AnonCredsRsHolderServi import { AnonCredsRsIssuerService } from '../src/services/AnonCredsRsIssuerService' import { AnonCredsRsVerifierService } from '../src/services/AnonCredsRsVerifierService' -const registry = new InMemoryAnonCredsRegistry({ useLegacyIdentifiers: false }) +const registry = new InMemoryAnonCredsRegistry() const anonCredsModuleConfig = new AnonCredsModuleConfig({ registries: [registry], }) diff --git a/packages/anoncreds-rs/tests/indy-flow.test.ts b/packages/anoncreds-rs/tests/indy-flow.test.ts index d5768f79ca..e254ee7dc6 100644 --- a/packages/anoncreds-rs/tests/indy-flow.test.ts +++ b/packages/anoncreds-rs/tests/indy-flow.test.ts @@ -2,6 +2,10 @@ import type { AnonCredsCredentialRequest } from '@aries-framework/anoncreds' import type { Wallet } from '@aries-framework/core' import { + getUnqualifiedSchemaId, + parseIndySchemaId, + getUnqualifiedCredentialDefinitionId, + parseIndyCredentialDefinitionId, AnonCredsModuleConfig, LegacyIndyCredentialFormatService, AnonCredsHolderServiceSymbol, @@ -38,7 +42,7 @@ import { AnonCredsRsHolderService } from '../src/services/AnonCredsRsHolderServi import { AnonCredsRsIssuerService } from '../src/services/AnonCredsRsIssuerService' import { AnonCredsRsVerifierService } from '../src/services/AnonCredsRsVerifierService' -const registry = new InMemoryAnonCredsRegistry({ useLegacyIdentifiers: true }) +const registry = new InMemoryAnonCredsRegistry() const anonCredsModuleConfig = new AnonCredsModuleConfig({ registries: [registry], }) @@ -70,7 +74,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 = 'LjgpST2rjsoxYegQDRm7EL' +const indyDid = 'did:indy:bcovrin:test:LjgpST2rjsoxYegQDRm7EL' // FIXME: Re-include in tests when NodeJS wrapper performance is improved describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', () => { @@ -191,6 +195,20 @@ describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', }), ] + const parsedCredentialDefinition = parseIndyCredentialDefinitionId(credentialDefinitionState.credentialDefinitionId) + const unqualifiedCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + parsedCredentialDefinition.namespaceIdentifier, + parsedCredentialDefinition.schemaSeqNo, + parsedCredentialDefinition.tag + ) + + const parsedSchemaId = parseIndySchemaId(schemaState.schemaId) + const unqualifiedSchemaId = getUnqualifiedSchemaId( + parsedSchemaId.namespaceIdentifier, + parsedSchemaId.schemaName, + parsedSchemaId.schemaVersion + ) + // Holder creates proposal holderCredentialRecord.credentialAttributes = credentialAttributes const { attachment: proposalAttachment } = await legacyIndyCredentialFormatService.createProposal(agentContext, { @@ -198,7 +216,7 @@ describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', credentialFormats: { indy: { attributes: credentialAttributes, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, }, }, }) @@ -266,8 +284,8 @@ describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', age: '25', name: 'John', }, - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + schemaId: unqualifiedSchemaId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, revocationRegistryId: null, credentialRevocationId: undefined, // FIXME: should be null? methodName: 'inMemory', @@ -275,8 +293,8 @@ describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', expect(holderCredentialRecord.metadata.data).toEqual({ '_anoncreds/credential': { - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + schemaId: unqualifiedSchemaId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, }, '_anoncreds/credentialRequest': { link_secret_blinding_data: expect.any(Object), @@ -287,8 +305,8 @@ describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', expect(issuerCredentialRecord.metadata.data).toEqual({ '_anoncreds/credential': { - schemaId: schemaState.schemaId, - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + schemaId: unqualifiedSchemaId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, }, }) @@ -309,14 +327,14 @@ describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', attributes: [ { name: 'name', - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, value: 'John', referent: '1', }, ], predicates: [ { - credentialDefinitionId: credentialDefinitionState.credentialDefinitionId, + credentialDefinitionId: unqualifiedCredentialDefinitionId, name: 'age', predicate: '>=', threshold: 18, diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts index 6c6630c6f7..98a45b70bc 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -45,7 +45,6 @@ import { AnonCredsError } from '../error' import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' import { AnonCredsIssuerServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' -import { legacyIndyCredentialDefinitionIdRegex, legacyIndySchemaIdRegex } from '../utils' import { convertAttributesToCredentialValues, assertCredentialValuesMatch, @@ -53,6 +52,7 @@ import { assertAttributesMatch, createAndLinkAttachmentsToPreview, } from '../utils/credential' +import { isUnqualifiedCredentialDefinitionId, isUnqualifiedSchemaId } from '../utils/indyIdentifiers' import { AnonCredsCredentialMetadataKey, AnonCredsCredentialRequestMetadataKey } from '../utils/metadata' import { generateLegacyProverDidLikeString } from '../utils/proverDid' @@ -151,7 +151,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic ) } - if (!credentialDefinitionId.match(legacyIndyCredentialDefinitionIdRegex)) { + if (!isUnqualifiedCredentialDefinitionId(credentialDefinitionId)) { throw new AriesFrameworkError(`${credentialDefinitionId} is not a valid legacy indy credential definition id`) } @@ -210,10 +210,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic const credOffer = attachment.getDataAsJson() - if ( - !credOffer.schema_id.match(legacyIndySchemaIdRegex) || - !credOffer.cred_def_id.match(legacyIndyCredentialDefinitionIdRegex) - ) { + if (!isUnqualifiedSchemaId(credOffer.schema_id) || !isUnqualifiedCredentialDefinitionId(credOffer.cred_def_id)) { throw new ProblemReportError('Invalid credential offer', { problemCode: CredentialProblemReportReason.IssuanceAbandoned, }) @@ -234,7 +231,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic const credentialOffer = offerAttachment.getDataAsJson() - if (!credentialOffer.cred_def_id.match(legacyIndyCredentialDefinitionIdRegex)) { + if (!isUnqualifiedCredentialDefinitionId(credentialOffer.cred_def_id)) { throw new AriesFrameworkError( `${credentialOffer.cred_def_id} is not a valid legacy indy credential definition id` ) diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts index ec493a8cc7..c912f5f692 100644 --- a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -55,11 +55,12 @@ import { checkValidCredentialValueEncoding, encodeCredentialValue, assertNoDuplicateGroupsNamesInProofRequest, - legacyIndyCredentialDefinitionIdRegex, - legacyIndySchemaIdRegex, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedSchemaIdRegex, getRevocationRegistriesForRequest, getRevocationRegistriesForProof, } from '../utils' +import { isUnqualifiedCredentialDefinitionId, isUnqualifiedSchemaId } from '../utils/indyIdentifiers' const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' @@ -468,7 +469,7 @@ export class LegacyIndyProofFormatService implements ProofFormatService { }), ] - const cd = parseCredentialDefinitionId(credentialDefinitionState.credentialDefinitionId) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.namespaceIdentifier, cd.schemaSeqNo, cd.tag) + const cd = parseIndyCredentialDefinitionId(credentialDefinitionState.credentialDefinitionId) + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + cd.namespaceIdentifier, + cd.schemaSeqNo, + cd.tag + ) - const s = parseSchemaId(schemaState.schemaId) - const legacySchemaId = getLegacySchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) + const s = parseIndySchemaId(schemaState.schemaId) + const legacySchemaId = getUnqualifiedSchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) // Holder creates proposal holderCredentialRecord.credentialAttributes = credentialAttributes diff --git a/packages/anoncreds/src/index.ts b/packages/anoncreds/src/index.ts index 2ad16e028a..edc9883578 100644 --- a/packages/anoncreds/src/index.ts +++ b/packages/anoncreds/src/index.ts @@ -12,5 +12,5 @@ export { AnonCredsModuleConfig, AnonCredsModuleConfigOptions } from './AnonCreds export { AnonCredsApi } from './AnonCredsApi' export * from './AnonCredsApiOptions' export { generateLegacyProverDidLikeString } from './utils/proverDid' -export * from './utils/legacyIndyIdentifiers' +export * from './utils/indyIdentifiers' export { assertBestPracticeRevocationInterval } from './utils/revocationInterval' diff --git a/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts index e6362c807b..78c65f302d 100644 --- a/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts +++ b/packages/anoncreds/src/protocols/credentials/v1/messages/V1ProposeCredentialMessage.ts @@ -5,10 +5,10 @@ import { Expose, Type } from 'class-transformer' import { IsInstance, IsOptional, IsString, Matches, ValidateNested } from 'class-validator' import { - legacyIndyCredentialDefinitionIdRegex, - legacyIndyDidRegex, - legacyIndySchemaIdRegex, - legacyIndySchemaVersionRegex, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedIndyDidRegex, + unqualifiedSchemaIdRegex, + unqualifiedSchemaVersionRegex, } from '../../../../utils' import { V1CredentialPreview } from './V1CredentialPreview' @@ -79,7 +79,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'schema_issuer_did' }) @IsString() @IsOptional() - @Matches(legacyIndyDidRegex) + @Matches(unqualifiedIndyDidRegex) public schemaIssuerDid?: string /** @@ -88,7 +88,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'schema_id' }) @IsString() @IsOptional() - @Matches(legacyIndySchemaIdRegex) + @Matches(unqualifiedSchemaIdRegex) public schemaId?: string /** @@ -105,7 +105,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'schema_version' }) @IsString() @IsOptional() - @Matches(legacyIndySchemaVersionRegex, { + @Matches(unqualifiedSchemaVersionRegex, { message: 'Version must be X.X or X.X.X', }) public schemaVersion?: string @@ -116,7 +116,7 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'cred_def_id' }) @IsString() @IsOptional() - @Matches(legacyIndyCredentialDefinitionIdRegex) + @Matches(unqualifiedCredentialDefinitionIdRegex) public credentialDefinitionId?: string /** @@ -125,6 +125,6 @@ export class V1ProposeCredentialMessage extends AgentMessage { @Expose({ name: 'issuer_did' }) @IsString() @IsOptional() - @Matches(legacyIndyDidRegex) + @Matches(unqualifiedIndyDidRegex) public issuerDid?: string } diff --git a/packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts b/packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts index 7e651dea57..47cbf8a636 100644 --- a/packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts +++ b/packages/anoncreds/src/protocols/proofs/v1/models/V1PresentationPreview.ts @@ -13,7 +13,7 @@ import { } from 'class-validator' import { anonCredsPredicateType, AnonCredsPredicateType } from '../../../../models' -import { legacyIndyCredentialDefinitionIdRegex } from '../../../../utils' +import { unqualifiedCredentialDefinitionIdRegex } from '../../../../utils' export interface V1PresentationPreviewAttributeOptions { name: string @@ -39,7 +39,7 @@ export class V1PresentationPreviewAttribute { @Expose({ name: 'cred_def_id' }) @IsString() @ValidateIf((o: V1PresentationPreviewAttribute) => o.referent !== undefined) - @Matches(legacyIndyCredentialDefinitionIdRegex) + @Matches(unqualifiedCredentialDefinitionIdRegex) public credentialDefinitionId?: string @Expose({ name: 'mime-type' }) @@ -82,7 +82,7 @@ export class V1PresentationPreviewPredicate { @Expose({ name: 'cred_def_id' }) @IsString() - @Matches(legacyIndyCredentialDefinitionIdRegex) + @Matches(unqualifiedCredentialDefinitionIdRegex) public credentialDefinitionId!: string @IsIn(anonCredsPredicateType) diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts index 2986566069..fe0bcc6eea 100644 --- a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRecord.ts @@ -4,6 +4,12 @@ import type { TagsBase } from '@aries-framework/core' import { BaseRecord, utils } from '@aries-framework/core' +import { + getUnqualifiedCredentialDefinitionId, + isDidIndyCredentialDefinitionId, + parseIndyCredentialDefinitionId, +} from '../utils/indyIdentifiers' + export interface AnonCredsCredentialDefinitionRecordProps { id?: string credentialDefinitionId: string @@ -17,6 +23,11 @@ export type DefaultAnonCredsCredentialDefinitionTags = { issuerId: string tag: string methodName: string + + // Stores the unqualified variant of the credential definition id, which allows issuing credentials using the legacy + // credential definition id, even though the credential definition id is stored in the wallet as a qualified id. + // This is only added when the credential definition id is an did:indy identifier. + unqualifiedCredentialDefinitionId?: string } export class AnonCredsCredentialDefinitionRecord extends BaseRecord< @@ -48,6 +59,13 @@ export class AnonCredsCredentialDefinitionRecord extends BaseRecord< } public getTags() { + let unqualifiedCredentialDefinitionId: string | undefined = undefined + if (isDidIndyCredentialDefinitionId(this.credentialDefinitionId)) { + const { namespaceIdentifier, schemaSeqNo, tag } = parseIndyCredentialDefinitionId(this.credentialDefinitionId) + + unqualifiedCredentialDefinitionId = getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) + } + return { ...this._tags, credentialDefinitionId: this.credentialDefinitionId, @@ -55,6 +73,7 @@ export class AnonCredsCredentialDefinitionRecord extends BaseRecord< issuerId: this.credentialDefinition.issuerId, tag: this.credentialDefinition.tag, methodName: this.methodName, + unqualifiedCredentialDefinitionId, } } } diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts index 7677dd76b8..88aedef82a 100644 --- a/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts +++ b/packages/anoncreds/src/repository/AnonCredsCredentialDefinitionRepository.ts @@ -14,10 +14,28 @@ export class AnonCredsCredentialDefinitionRepository extends Repository } public async getBySchemaId(agentContext: AgentContext, schemaId: string) { - return this.getSingleByQuery(agentContext, { schemaId: schemaId }) + return this.getSingleByQuery(agentContext, { + $or: [ + { + schemaId, + }, + { + unqualifiedSchemaId: schemaId, + }, + ], + }) } public async findBySchemaId(agentContext: AgentContext, schemaId: string) { - return await this.findSingleByQuery(agentContext, { schemaId: schemaId }) + return await this.findSingleByQuery(agentContext, { + $or: [ + { + schemaId, + }, + { + unqualifiedSchemaId: schemaId, + }, + ], + }) } } diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index 7bfe190380..a657279715 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -61,13 +61,13 @@ export interface GetCredentialOptions { } export interface GetCredentialsOptions { - credentialDefinitionId: string - schemaId: string - schemaIssuerId: string - schemaName: string - schemaVersion: string - issuerId: string - methodName: string + credentialDefinitionId?: string + schemaId?: string + schemaIssuerId?: string + schemaName?: string + schemaVersion?: string + issuerId?: string + methodName?: string } // TODO: Maybe we can make this a bit more specific? diff --git a/packages/anoncreds/src/updates/__tests__/__snapshots__/0.3.test.ts.snap b/packages/anoncreds/src/updates/__tests__/__snapshots__/0.3.test.ts.snap index d3a5f4c2a5..1aacd46acc 100644 --- a/packages/anoncreds/src/updates/__tests__/__snapshots__/0.3.test.ts.snap +++ b/packages/anoncreds/src/updates/__tests__/__snapshots__/0.3.test.ts.snap @@ -365,6 +365,7 @@ exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the "methodName": "indy", "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/AnotherSchema/5.12", "tag": "TAG2222", + "unqualifiedCredentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728266:TAG2222", }, "type": "AnonCredsCredentialDefinitionRecord", "value": { @@ -463,6 +464,7 @@ exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the "methodName": "indy", "schemaId": "did:indy:bcovrin:test:A4CYPASJYRZRt98YWrac3H/anoncreds/v0/SCHEMA/Test Schema/5.0", "tag": "TAG", + "unqualifiedCredentialDefinitionId": "A4CYPASJYRZRt98YWrac3H:3:CL:728265:TAG", }, "type": "AnonCredsCredentialDefinitionRecord", "value": { @@ -699,6 +701,7 @@ exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the "schemaIssuerDid": undefined, "schemaName": "AnotherSchema", "schemaVersion": "5.12", + "unqualifiedSchemaId": "A4CYPASJYRZRt98YWrac3H:2:AnotherSchema:5.12", }, "type": "AnonCredsSchemaRecord", "value": { @@ -797,6 +800,7 @@ exports[`UpdateAssistant | AnonCreds | v0.3.1 - v0.4 should correctly update the "schemaIssuerDid": undefined, "schemaName": "Test Schema", "schemaVersion": "5.0", + "unqualifiedSchemaId": "A4CYPASJYRZRt98YWrac3H:2:Test Schema:5.0", }, "type": "AnonCredsSchemaRecord", "value": { diff --git a/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts b/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts new file mode 100644 index 0000000000..7fba68562d --- /dev/null +++ b/packages/anoncreds/src/utils/__tests__/indyIdentifiers.test.ts @@ -0,0 +1,147 @@ +import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedRevocationRegistryId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndyRevocationRegistryId, + parseIndySchemaId, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedIndyDidRegex, + unqualifiedRevocationRegistryIdRegex, + unqualifiedSchemaIdRegex, + unqualifiedSchemaVersionRegex, +} from '../indyIdentifiers' + +describe('Legacy Indy Identifier Regex', () => { + const invalidTest = 'test' + + test('test for legacyIndyCredentialDefinitionIdRegex', async () => { + const test = 'q7ATwTYbQDgiigVijUAej:3:CL:160971:1.0.0' + expect(test).toMatch(unqualifiedCredentialDefinitionIdRegex) + expect(unqualifiedCredentialDefinitionIdRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndyDidRegex', async () => { + const test = 'did:sov:q7ATwTYbQDgiigVijUAej' + expect(test).toMatch(unqualifiedIndyDidRegex) + expect(unqualifiedIndyDidRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndySchemaIdRegex', async () => { + const test = 'q7ATwTYbQDgiigVijUAej:2:test:1.0' + expect(test).toMatch(unqualifiedSchemaIdRegex) + expect(unqualifiedSchemaIdRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndySchemaIdRegex', async () => { + const test = 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + expect(test).toMatch(unqualifiedRevocationRegistryIdRegex) + expect(unqualifiedRevocationRegistryIdRegex.test(invalidTest)).toBeFalsy() + }) + + test('test for legacyIndySchemaVersionRegex', async () => { + const test = '1.0.0' + expect(test).toMatch(unqualifiedSchemaVersionRegex) + expect(unqualifiedSchemaVersionRegex.test(invalidTest)).toBeFalsy() + }) + + test('getUnqualifiedSchemaId returns a valid schema id given a did, name, and version', () => { + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getUnqualifiedSchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + test('getUnqualifiedCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getUnqualifiedCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) + + test('getUnqualifiedRevocationRegistryId 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(getUnqualifiedRevocationRegistryId(did, seqNo, credentialDefinitionTag, tag)).toEqual( + '12345:4:12345:3:CL:420:someTag:CL_ACCUM:anotherTag' + ) + }) + + describe('parseIndySchemaId', () => { + test('parses legacy schema id', () => { + expect(parseIndySchemaId('SDqTzbVuCowusqGBNbNDjH:2:schema-name:1.0')).toEqual({ + did: 'SDqTzbVuCowusqGBNbNDjH', + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + schemaName: 'schema-name', + schemaVersion: '1.0', + }) + }) + + test('parses did:indy schema id', () => { + expect( + parseIndySchemaId('did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0') + ).toEqual({ + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + did: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', + schemaName: 'schema-name', + schemaVersion: '1.0', + namespace: 'bcovrin:test', + }) + }) + }) + + describe('parseIndyCredentialDefinitionId', () => { + test('parses legacy credential definition id', () => { + expect(parseIndyCredentialDefinitionId('TL1EaPFCZ8Si5aUrqScBDt:3:CL:10:TAG')).toEqual({ + did: 'TL1EaPFCZ8Si5aUrqScBDt', + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + schemaSeqNo: '10', + tag: 'TAG', + }) + }) + + test('parses did:indy credential definition id', () => { + expect( + parseIndyCredentialDefinitionId('did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/10/TAG') + ).toEqual({ + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + did: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', + namespace: 'pool:localtest', + schemaSeqNo: '10', + tag: 'TAG', + }) + }) + }) + + describe('parseIndyRevocationRegistryId', () => { + test('parses legacy revocation registry id', () => { + expect( + parseIndyRevocationRegistryId('5nDyJVP1NrcPAttP3xwMB9:4:5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npdb:CL_ACCUM:TAG1') + ).toEqual({ + did: '5nDyJVP1NrcPAttP3xwMB9', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + schemaSeqNo: '56495', + credentialDefinitionTag: 'npdb', + revocationRegistryTag: 'TAG1', + }) + }) + + test('parses did:indy revocation registry id', () => { + expect( + parseIndyRevocationRegistryId('did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1') + ).toEqual({ + namespace: 'sovrin', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + did: 'did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9', + schemaSeqNo: '56495', + credentialDefinitionTag: 'npdb', + revocationRegistryTag: 'TAG1', + }) + }) + }) +}) diff --git a/packages/anoncreds/src/utils/__tests__/legacyIndyIdentifiers.test.ts b/packages/anoncreds/src/utils/__tests__/legacyIndyIdentifiers.test.ts deleted file mode 100644 index 2d38390ae9..0000000000 --- a/packages/anoncreds/src/utils/__tests__/legacyIndyIdentifiers.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - legacyIndyCredentialDefinitionIdRegex, - legacyIndyDidRegex, - legacyIndySchemaIdRegex, - legacyIndySchemaVersionRegex, -} from '../legacyIndyIdentifiers' - -describe('Legacy Indy Identifier Regex', () => { - const invalidTest = 'test' - - test('test for legacyIndyCredentialDefinitionIdRegex', async () => { - const test = 'q7ATwTYbQDgiigVijUAej:3:CL:160971:1.0.0' - expect(test).toMatch(legacyIndyCredentialDefinitionIdRegex) - expect(legacyIndyCredentialDefinitionIdRegex.test(invalidTest)).toBeFalsy() - }) - - test('test for legacyIndyDidRegex', async () => { - const test = 'did:sov:q7ATwTYbQDgiigVijUAej' - expect(test).toMatch(legacyIndyDidRegex) - expect(legacyIndyDidRegex.test(invalidTest)).toBeFalsy - }) - - test('test for legacyIndySchemaIdRegex', async () => { - const test = 'q7ATwTYbQDgiigVijUAej:2:test:1.0' - expect(test).toMatch(legacyIndySchemaIdRegex) - expect(legacyIndySchemaIdRegex.test(invalidTest)).toBeFalsy - }) - - test('test for legacyIndySchemaVersionRegex', async () => { - const test = '1.0.0' - expect(test).toMatch(legacyIndySchemaVersionRegex) - expect(legacyIndySchemaVersionRegex.test(invalidTest)).toBeFalsy - }) -}) diff --git a/packages/anoncreds/src/utils/index.ts b/packages/anoncreds/src/utils/index.ts index 7fa0da87ed..7f2d7763fe 100644 --- a/packages/anoncreds/src/utils/index.ts +++ b/packages/anoncreds/src/utils/index.ts @@ -10,8 +10,8 @@ export { IsMap } from './isMap' export { composeCredentialAutoAccept, composeProofAutoAccept } from './composeAutoAccept' export { areCredentialPreviewAttributesEqual } from './credentialPreviewAttributes' export { - legacyIndyCredentialDefinitionIdRegex, - legacyIndyDidRegex, - legacyIndySchemaIdRegex, - legacyIndySchemaVersionRegex, -} from './legacyIndyIdentifiers' + unqualifiedCredentialDefinitionIdRegex, + unqualifiedIndyDidRegex, + unqualifiedSchemaIdRegex, + unqualifiedSchemaVersionRegex, +} from './indyIdentifiers' diff --git a/packages/anoncreds/src/utils/indyIdentifiers.ts b/packages/anoncreds/src/utils/indyIdentifiers.ts new file mode 100644 index 0000000000..1e20f75c55 --- /dev/null +++ b/packages/anoncreds/src/utils/indyIdentifiers.ts @@ -0,0 +1,196 @@ +import { AriesFrameworkError } from '@aries-framework/core' + +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/ + +// :2:: +export const unqualifiedSchemaIdRegex = /^([a-zA-Z0-9]{21,22}):2:(.+):([0-9.]+)$/ +// did:indy::/anoncreds/v0/SCHEMA// +export const didIndySchemaIdRegex = new RegExp(`^${didIndyAnonCredsBase.source}/SCHEMA/(.+)/([0-9.]+)$`) + +export const unqualifiedSchemaVersionRegex = /^(\d+\.)?(\d+\.)?(\*|\d+)$/ +export const unqualifiedIndyDidRegex = /^(did:sov:)?[a-zA-Z0-9]{21,22}$/ + +// :3:CL:: +export const unqualifiedCredentialDefinitionIdRegex = /^([a-zA-Z0-9]{21,22}):3:CL:([1-9][0-9]*):(.+)$/ +// did:indy::/anoncreds/v0/CLAIM_DEF// +export const didIndyCredentialDefinitionIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/CLAIM_DEF/([1-9][0-9]*)/(.+)$` +) + +// :4::3:CL::CL_ACCUM: +export const unqualifiedRevocationRegistryIdRegex = + /^([a-zA-Z0-9]{21,22}):4:[a-zA-Z0-9]{21,22}:3:CL:([1-9][0-9]*):(.+):CL_ACCUM:(.+)$/ +// did:indy::/anoncreds/v0/REV_REG_DEF/// +export const didIndyRevocationRegistryIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/REV_REG_DEF/([1-9][0-9]*)/(.+)/(.+)$` +) + +export const didIndyRegex = /^did:indy:((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?):([1-9A-HJ-NP-Za-km-z]{21,22})$/ + +export function getUnqualifiedSchemaId(unqualifiedDid: string, name: string, version: string) { + return `${unqualifiedDid}:2:${name}:${version}` +} + +export function getUnqualifiedCredentialDefinitionId(unqualifiedDid: string, seqNo: string | number, tag: string) { + return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` +} + +// TZQuLp43UcYTdtc3HewcDz:4:TZQuLp43UcYTdtc3HewcDz:3:CL:98158:BaustellenzertifikateNU1:CL_ACCUM:1-100 +export function getUnqualifiedRevocationRegistryId( + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `${unqualifiedDid}:4:${unqualifiedDid}:3:CL:${seqNo}:${credentialDefinitionTag}:CL_ACCUM:${revocationRegistryTag}` +} + +export function isUnqualifiedCredentialDefinitionId(credentialDefinitionId: string) { + return unqualifiedCredentialDefinitionIdRegex.test(credentialDefinitionId) +} + +export function isUnqualifiedRevocationRegistryId(revocationRegistryId: string) { + return unqualifiedRevocationRegistryIdRegex.test(revocationRegistryId) +} + +export function isUnqualifiedSchemaId(schemaId: string) { + return unqualifiedSchemaIdRegex.test(schemaId) +} + +export function isDidIndySchemaId(schemaId: string) { + return didIndySchemaIdRegex.test(schemaId) +} + +export function isDidIndyCredentialDefinitionId(credentialDefinitionId: string) { + return didIndyCredentialDefinitionIdRegex.test(credentialDefinitionId) +} + +export function isDidIndyRevocationRegistryId(revocationRegistryId: string) { + return didIndyRevocationRegistryIdRegex.test(revocationRegistryId) +} + +export function parseIndyDid(did: string) { + const match = did.match(didIndyRegex) + if (match) { + const [, namespace, namespaceIdentifier] = match + return { namespace, namespaceIdentifier } + } else { + throw new AriesFrameworkError(`${did} is not a valid did:indy did`) + } +} + +interface ParsedIndySchemaId { + did: string + namespaceIdentifier: string + schemaName: string + schemaVersion: string + namespace?: string +} + +export function parseIndySchemaId(schemaId: string): ParsedIndySchemaId { + const didIndyMatch = schemaId.match(didIndySchemaIdRegex) + if (didIndyMatch) { + const [, did, namespace, namespaceIdentifier, schemaName, schemaVersion] = didIndyMatch + + return { + did, + namespaceIdentifier, + schemaName, + schemaVersion, + namespace, + } + } + + const legacyMatch = schemaId.match(unqualifiedSchemaIdRegex) + if (legacyMatch) { + const [, did, schemaName, schemaVersion] = legacyMatch + + return { + did, + namespaceIdentifier: did, + schemaName, + schemaVersion, + } + } + + throw new Error(`Invalid schema id: ${schemaId}`) +} + +interface ParsedIndyCredentialDefinitionId { + did: string + namespaceIdentifier: string + schemaSeqNo: string + tag: string + namespace?: string +} + +export function parseIndyCredentialDefinitionId(credentialDefinitionId: string): ParsedIndyCredentialDefinitionId { + const didIndyMatch = credentialDefinitionId.match(didIndyCredentialDefinitionIdRegex) + if (didIndyMatch) { + const [, did, namespace, namespaceIdentifier, schemaSeqNo, tag] = didIndyMatch + + return { + did, + namespaceIdentifier, + schemaSeqNo, + tag, + namespace, + } + } + + const legacyMatch = credentialDefinitionId.match(unqualifiedCredentialDefinitionIdRegex) + if (legacyMatch) { + const [, did, schemaSeqNo, tag] = legacyMatch + + return { + did, + namespaceIdentifier: did, + schemaSeqNo, + tag, + } + } + + throw new Error(`Invalid credential definition id: ${credentialDefinitionId}`) +} + +interface ParsedIndyRevocationRegistryId { + did: string + namespaceIdentifier: string + schemaSeqNo: string + credentialDefinitionTag: string + revocationRegistryTag: string + namespace?: string +} + +export function parseIndyRevocationRegistryId(revocationRegistryId: string): ParsedIndyRevocationRegistryId { + const didIndyMatch = revocationRegistryId.match(didIndyRevocationRegistryIdRegex) + if (didIndyMatch) { + const [, did, namespace, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag] = + didIndyMatch + + return { + did, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag, + namespace, + } + } + + const legacyMatch = revocationRegistryId.match(unqualifiedRevocationRegistryIdRegex) + if (legacyMatch) { + const [, did, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag] = legacyMatch + + return { + did, + namespaceIdentifier: did, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag, + } + } + + throw new Error(`Invalid revocation registry id: ${revocationRegistryId}`) +} diff --git a/packages/anoncreds/src/utils/legacyIndyIdentifiers.ts b/packages/anoncreds/src/utils/legacyIndyIdentifiers.ts deleted file mode 100644 index 29cc3f45d6..0000000000 --- a/packages/anoncreds/src/utils/legacyIndyIdentifiers.ts +++ /dev/null @@ -1,5 +0,0 @@ -export const legacyIndySchemaIdRegex = /^[a-zA-Z0-9]{21,22}:2:.+:[0-9.]+$/ -export const legacyIndySchemaVersionRegex = /^(\d+\.)?(\d+\.)?(\*|\d+)$/ -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 legacyIndyDidRegex = /^(did:sov:)?[a-zA-Z0-9]{21,22}$/ diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts index 7ee471b4e1..9cb3a9adf3 100644 --- a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -19,14 +19,8 @@ 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' +import { getDidIndyCredentialDefinitionId, getDidIndySchemaId } from '../../indy-sdk/src/anoncreds/utils/identifiers' +import { getUnqualifiedCredentialDefinitionId, getUnqualifiedSchemaId, parseIndyDid, parseIndySchemaId } from '../src' /** * In memory implementation of the {@link AnonCredsRegistry} interface. Useful for testing. @@ -43,34 +37,30 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { private credentialDefinitions: Record private revocationRegistryDefinitions: Record private revocationStatusLists: Record> - private useLegacyIdentifiers: boolean public constructor({ existingSchemas = {}, existingCredentialDefinitions = {}, existingRevocationRegistryDefinitions = {}, existingRevocationStatusLists = {}, - useLegacyIdentifiers = false, }: { existingSchemas?: Record existingCredentialDefinitions?: Record existingRevocationRegistryDefinitions?: Record existingRevocationStatusLists?: Record> - useLegacyIdentifiers?: boolean } = {}) { this.schemas = existingSchemas this.credentialDefinitions = existingCredentialDefinitions this.revocationRegistryDefinitions = existingRevocationRegistryDefinitions this.revocationStatusLists = existingRevocationStatusLists - this.useLegacyIdentifiers = useLegacyIdentifiers } public async getSchema(agentContext: AgentContext, schemaId: string): Promise { const schema = this.schemas[schemaId] - const parsed = parseSchemaId(schemaId) + const parsed = parseIndySchemaId(schemaId) - const legacySchemaId = getLegacySchemaId(parsed.namespaceIdentifier, parsed.schemaName, parsed.schemaVersion) + const legacySchemaId = getUnqualifiedSchemaId(parsed.namespaceIdentifier, parsed.schemaName, parsed.schemaVersion) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) if (!schema) { @@ -100,18 +90,17 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { agentContext: AgentContext, options: RegisterSchemaOptions ): Promise { - let legacyIssuerId - let didIndySchemaId = '' - if (this.useLegacyIdentifiers) { - legacyIssuerId = options.schema.issuerId - } else { - const { namespace, namespaceIdentifier } = parseIndyDid(options.schema.issuerId) - legacyIssuerId = namespaceIdentifier - didIndySchemaId = getDidIndySchemaId(namespace, namespaceIdentifier, options.schema.name, options.schema.version) - this.schemas[didIndySchemaId] = options.schema - } + const { namespace, namespaceIdentifier } = parseIndyDid(options.schema.issuerId) + const legacyIssuerId = namespaceIdentifier + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + this.schemas[didIndySchemaId] = options.schema - const legacySchemaId = getLegacySchemaId(legacyIssuerId, options.schema.name, options.schema.version) + const legacySchemaId = getUnqualifiedSchemaId(legacyIssuerId, options.schema.name, options.schema.version) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) this.schemas[legacySchemaId] = { @@ -129,7 +118,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { schemaState: { state: 'finished', schema: options.schema, - schemaId: this.useLegacyIdentifiers ? legacySchemaId : didIndySchemaId, + schemaId: didIndySchemaId, }, } } @@ -163,32 +152,26 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { agentContext: AgentContext, options: RegisterCredentialDefinitionOptions ): Promise { - const parsedSchema = parseSchemaId(options.credentialDefinition.schemaId) - const legacySchemaId = getLegacySchemaId( + const parsedSchema = parseIndySchemaId(options.credentialDefinition.schemaId) + const legacySchemaId = getUnqualifiedSchemaId( parsedSchema.namespaceIdentifier, parsedSchema.schemaName, parsedSchema.schemaVersion ) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) - let legacyIssuerId - let didIndyCredentialDefinitionId = '' - if (this.useLegacyIdentifiers) { - legacyIssuerId = options.credentialDefinition.issuerId - } else { - const { namespace, namespaceIdentifier } = parseIndyDid(options.credentialDefinition.issuerId) - legacyIssuerId = namespaceIdentifier - didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( - namespace, - namespaceIdentifier, - indyLedgerSeqNo, - options.credentialDefinition.tag - ) + const { namespace, namespaceIdentifier } = parseIndyDid(options.credentialDefinition.issuerId) + const legacyIssuerId = namespaceIdentifier + const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + namespaceIdentifier, + indyLedgerSeqNo, + options.credentialDefinition.tag + ) - this.credentialDefinitions[didIndyCredentialDefinitionId] = options.credentialDefinition - } + this.credentialDefinitions[didIndyCredentialDefinitionId] = options.credentialDefinition - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( legacyIssuerId, indyLedgerSeqNo, options.credentialDefinition.tag @@ -206,9 +189,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { credentialDefinitionState: { state: 'finished', credentialDefinition: options.credentialDefinition, - credentialDefinitionId: this.useLegacyIdentifiers - ? legacyCredentialDefinitionId - : didIndyCredentialDefinitionId, + credentialDefinitionId: didIndyCredentialDefinitionId, }, } } diff --git a/packages/anoncreds/tests/anoncreds.test.ts b/packages/anoncreds/tests/anoncreds.test.ts index db7c6e1def..d5590ca4ba 100644 --- a/packages/anoncreds/tests/anoncreds.test.ts +++ b/packages/anoncreds/tests/anoncreds.test.ts @@ -165,6 +165,7 @@ describe('AnonCreds API', () => { schemaName: 'Employee Credential', schemaVersion: '1.0.0', methodName: 'inMemory', + unqualifiedSchemaId: '6xDN7v3AiGgusRp4bqZACZ:2:Employee Credential:1.0.0', }) }) @@ -263,6 +264,7 @@ describe('AnonCreds API', () => { schemaId: '7Cd2Yj9yEZNcmNoH54tq9i:2:Test Schema:1.0.0', issuerId: 'did:indy:pool:localhost:VsKV7grR1BUE29mG2Fm2kX', tag: 'TAG', + unqualifiedCredentialDefinitionId: 'VsKV7grR1BUE29mG2Fm2kX:3:CL:75206:TAG', }) }) diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 571ecc74eb..7565352e10 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -51,12 +51,6 @@ 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, @@ -67,6 +61,10 @@ import { } from '../../indy-vdr/src' import { indyVdrModuleConfig } from '../../indy-vdr/tests/helpers' import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndySchemaId, V1CredentialProtocol, V1ProofProtocol, AnonCredsModule, @@ -469,11 +467,15 @@ export async function prepareForAnonCredsIssuance(agent: Agent, { attributeNames tag: 'default', }) - const s = parseSchemaId(schema.schemaId) - const cd = parseCredentialDefinitionId(credentialDefinition.credentialDefinitionId) + const s = parseIndySchemaId(schema.schemaId) + const cd = parseIndyCredentialDefinitionId(credentialDefinition.credentialDefinitionId) - const legacySchemaId = getLegacySchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.namespaceIdentifier, cd.schemaSeqNo, cd.tag) + const legacySchemaId = getUnqualifiedSchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( + cd.namespaceIdentifier, + cd.schemaSeqNo, + cd.tag + ) // Wait some time pass to let ledger settle the object await sleep(1000) diff --git a/packages/askar/src/storage/AskarStorageService.ts b/packages/askar/src/storage/AskarStorageService.ts index 3c4dcda0ec..2174291c81 100644 --- a/packages/askar/src/storage/AskarStorageService.ts +++ b/packages/askar/src/storage/AskarStorageService.ts @@ -159,7 +159,7 @@ export class AskarStorageService implements StorageService } return instances } catch (error) { - throw new WalletError(`Error executing query`, { cause: error }) + throw new WalletError(`Error executing query. ${error.message}`, { cause: error }) } } } 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 7f5abe619f..3a7b6dac2e 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 @@ -126,7 +126,7 @@ const getIndyJsonLdModules = () => cache: new CacheModule({ cache: new InMemoryLruCache({ limit: 100 }), }), - w3cVc: new W3cCredentialsModule({ + w3cCredentials: new W3cCredentialsModule({ documentLoader: customDocumentLoader, }), } as const) diff --git a/packages/core/src/modules/vc/W3cCredentialsModule.ts b/packages/core/src/modules/vc/W3cCredentialsModule.ts index 70b93e81b6..3c6886fdf4 100644 --- a/packages/core/src/modules/vc/W3cCredentialsModule.ts +++ b/packages/core/src/modules/vc/W3cCredentialsModule.ts @@ -1,4 +1,4 @@ -import type { W3cVcModuleConfigOptions } from './W3cCredentialsModuleConfig' +import type { W3cCredentialsModuleConfigOptions } from './W3cCredentialsModuleConfig' import type { DependencyManager, Module } from '../../plugins' import { KeyType } from '../../crypto' @@ -21,7 +21,7 @@ export class W3cCredentialsModule implements Module { public readonly config: W3cCredentialsModuleConfig public readonly api = W3cCredentialsApi - public constructor(config?: W3cVcModuleConfigOptions) { + public constructor(config?: W3cCredentialsModuleConfigOptions) { this.config = new W3cCredentialsModuleConfig(config) } diff --git a/packages/core/src/modules/vc/W3cCredentialsModuleConfig.ts b/packages/core/src/modules/vc/W3cCredentialsModuleConfig.ts index c9b6fbdd2f..ef7c5e5947 100644 --- a/packages/core/src/modules/vc/W3cCredentialsModuleConfig.ts +++ b/packages/core/src/modules/vc/W3cCredentialsModuleConfig.ts @@ -3,10 +3,10 @@ import type { DocumentLoaderWithContext } from './libraries/documentLoader' import { defaultDocumentLoader } from './libraries/documentLoader' /** - * W3cVcModuleConfigOptions defines the interface for the options of the W3cVcModuleConfig class. + * W3cCredentialsModuleConfigOptions defines the interface for the options of the W3cCredentialsModuleConfig class. * This can contain optional parameters that have default values in the config class itself. */ -export interface W3cVcModuleConfigOptions { +export interface W3cCredentialsModuleConfigOptions { /** * Document loader to use for resolving JSON-LD objects. Takes a {@link AgentContext} as parameter, * and must return a {@link DocumentLoader} function. @@ -33,13 +33,13 @@ export interface W3cVcModuleConfigOptions { } export class W3cCredentialsModuleConfig { - private options: W3cVcModuleConfigOptions + private options: W3cCredentialsModuleConfigOptions - public constructor(options?: W3cVcModuleConfigOptions) { + public constructor(options?: W3cCredentialsModuleConfigOptions) { this.options = options ?? {} } - /** See {@link W3cVcModuleConfigOptions.documentLoader} */ + /** See {@link W3cCredentialsModuleConfigOptions.documentLoader} */ public get documentLoader() { return this.options.documentLoader ?? defaultDocumentLoader } diff --git a/packages/core/src/modules/vc/__tests__/W3cCredentialsApi.test.ts b/packages/core/src/modules/vc/__tests__/W3cCredentialsApi.test.ts index f939a40ccd..1af3b044b1 100644 --- a/packages/core/src/modules/vc/__tests__/W3cCredentialsApi.test.ts +++ b/packages/core/src/modules/vc/__tests__/W3cCredentialsApi.test.ts @@ -3,18 +3,23 @@ import { getAgentOptions, indySdk } from '../../../../tests' import { Agent } from '../../../agent/Agent' import { JsonTransformer } from '../../../utils' import { W3cCredentialService } from '../W3cCredentialService' +import { W3cCredentialsModule } from '../W3cCredentialsModule' import { W3cVerifiableCredential } from '../models' import { W3cCredentialRepository } from '../repository' +import { customDocumentLoader } from './documentLoader' import { Ed25519Signature2018Fixtures } from './fixtures' const modules = { indySdk: new IndySdkModule({ indySdk, }), + w3cCredentials: new W3cCredentialsModule({ + documentLoader: customDocumentLoader, + }), } -const agentOptions = getAgentOptions('W3cCredentialsApi', {}, modules) +const agentOptions = getAgentOptions('W3cCredentialsApi', {}, modules) const agent = new Agent(agentOptions) @@ -83,7 +88,7 @@ describe('W3cCredentialsApi', () => { expect(repoSpy).toHaveBeenCalledTimes(1) expect(serviceSpy).toHaveBeenCalledTimes(1) - expect(serviceSpy).toHaveBeenCalledWith((agent as any).agentContext, storedCredential.id) + expect(serviceSpy).toHaveBeenCalledWith(agent.context, storedCredential.id) const allCredentials = await agent.w3cCredentials.getAllCredentialRecords() expect(allCredentials).toHaveLength(0) diff --git a/packages/core/tests/jsonld.ts b/packages/core/tests/jsonld.ts index 5ac2385157..41ea8013b4 100644 --- a/packages/core/tests/jsonld.ts +++ b/packages/core/tests/jsonld.ts @@ -33,7 +33,7 @@ export const getJsonLdModules = ({ credentialProtocols: [new V2CredentialProtocol({ credentialFormats: [new JsonLdCredentialFormatService()] })], autoAcceptCredentials, }), - w3cVc: new W3cCredentialsModule({ + w3cCredentials: new W3cCredentialsModule({ documentLoader: customDocumentLoader, }), proofs: new ProofsModule({ diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index 733e08190c..21ab95ab53 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -14,20 +14,24 @@ import type { import type { AgentContext } from '@aries-framework/core' import type { Schema as IndySdkSchema } from 'indy-sdk' -import { parseIndyDid, verificationKeyForIndyDid } from '../../dids/didIndyUtil' +import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedRevocationRegistryId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndyDid, + parseIndyRevocationRegistryId, + parseIndySchemaId, +} from '@aries-framework/anoncreds' + +import { verificationKeyForIndyDid } from '../../dids/didIndyUtil' import { IndySdkError, isIndyError } from '../../error' import { IndySdkPoolService } from '../../ledger' import { IndySdkSymbol } from '../../types' import { getDidIndyCredentialDefinitionId, getDidIndySchemaId, - getLegacyCredentialDefinitionId, - getLegacyRevocationRegistryId, - getLegacySchemaId, indySdkAnonCredsRegistryIdentifierRegex, - parseCredentialDefinitionId, - parseRevocationRegistryId, - parseSchemaId, } from '../utils/identifiers' import { anonCredsRevocationStatusListFromIndySdk } from '../utils/transform' @@ -47,12 +51,12 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) // parse schema id (supports did:indy and legacy) - const { did, namespaceIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) + const { did, namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(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(namespaceIdentifier, schemaName, schemaVersion) + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) const request = await indySdk.buildGetSchemaRequest(null, legacySchemaId) agentContext.config.logger.trace( @@ -126,7 +130,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { options.schema.name, options.schema.version ) - const legacySchemaId = getLegacySchemaId(namespaceIdentifier, options.schema.name, options.schema.version) + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, options.schema.name, options.schema.version) const schema = { attrNames: options.schema.attrNames, @@ -193,14 +197,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) // we support did:indy and legacy identifiers - const { did, namespaceIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) + const { did, namespaceIdentifier, schemaSeqNo, tag } = parseIndyCredentialDefinitionId(credentialDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve credential definition '${credentialDefinitionId}'` ) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) const request = await indySdk.buildGetCredDefRequest(null, legacyCredentialDefinitionId) agentContext.config.logger.trace( @@ -315,7 +319,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } } - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( namespaceIdentifier, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag @@ -380,14 +384,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) const { did, namespaceIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = - parseRevocationRegistryId(revocationRegistryDefinitionId) + parseIndyRevocationRegistryId(revocationRegistryDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` ) - const legacyRevocationRegistryId = getLegacyRevocationRegistryId( + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, @@ -422,7 +426,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { schemaSeqNo, credentialDefinitionTag ) - : getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) + : getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) return { resolutionMetadata: {}, @@ -474,14 +478,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = - parseRevocationRegistryId(revocationRegistryId) + parseIndyRevocationRegistryId(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( + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index 3fcb1edbb7..ef44edb2e2 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -24,13 +24,21 @@ import type { IndyProofRequest, } from 'indy-sdk' -import { AnonCredsLinkSecretRepository, generateLegacyProverDidLikeString } from '@aries-framework/anoncreds' +import { + parseIndyCredentialDefinitionId, + AnonCredsLinkSecretRepository, + generateLegacyProverDidLikeString, +} from '@aries-framework/anoncreds' import { AriesFrameworkError, injectable, inject, utils } from '@aries-framework/core' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' import { assertIndySdkWallet } from '../../utils/assertIndySdkWallet' -import { parseCredentialDefinitionId } from '../utils/identifiers' +import { + assertAllUnqualified, + assertUnqualifiedCredentialOffer, + assertUnqualifiedProofRequest, +} from '../utils/assertUnqualified' import { anonCredsCredentialRequestMetadataFromIndySdk, indySdkCredentialDefinitionFromAnonCreds, @@ -81,6 +89,13 @@ export class IndySdkHolderService implements AnonCredsHolderService { assertIndySdkWallet(agentContext.wallet) + // Make sure all identifiers are unqualified + assertAllUnqualified({ + schemaIds: Object.keys(options.schemas), + credentialDefinitionIds: Object.keys(options.credentialDefinitions), + revocationRegistryIds: Object.keys(options.revocationRegistries), + }) + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) try { @@ -106,7 +121,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { ) // Get the seqNo for the schemas so we can use it when transforming the schemas - const { schemaSeqNo } = parseCredentialDefinitionId(credentialDefinitionId) + const { schemaSeqNo } = parseIndyCredentialDefinitionId(credentialDefinitionId) seqNoMap[credentialDefinition.schemaId] = Number(schemaSeqNo) } @@ -153,6 +168,11 @@ export class IndySdkHolderService implements AnonCredsHolderService { public async storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { assertIndySdkWallet(agentContext.wallet) + assertAllUnqualified({ + schemaIds: [options.credentialDefinition.schemaId, options.credential.schema_id], + credentialDefinitionIds: [options.credentialDefinitionId, options.credential.cred_def_id], + revocationRegistryIds: [options.revocationRegistry?.id, options.credential.rev_reg_id], + }) const indyRevocationRegistryDefinition = options.revocationRegistry ? indySdkRevocationRegistryDefinitionFromAnonCreds( @@ -214,6 +234,12 @@ export class IndySdkHolderService implements AnonCredsHolderService { return [] } + assertAllUnqualified({ + credentialDefinitionIds: [options.credentialDefinitionId], + schemaIds: [options.schemaId], + issuerIds: [options.issuerId, options.schemaIssuerId], + }) + const credentials = await this.indySdk.proverGetCredentials(agentContext.wallet.handle, { cred_def_id: options.credentialDefinitionId, schema_id: options.schemaId, @@ -240,6 +266,12 @@ export class IndySdkHolderService implements AnonCredsHolderService { ): Promise { assertIndySdkWallet(agentContext.wallet) + assertUnqualifiedCredentialOffer(options.credentialOffer) + assertAllUnqualified({ + schemaIds: [options.credentialDefinition.schemaId], + issuerIds: [options.credentialDefinition.issuerId], + }) + if (!options.useLegacyProverDid) { throw new AriesFrameworkError('Indy SDK only supports legacy prover did for credential requests') } @@ -307,6 +339,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { options: GetCredentialsForProofRequestOptions ): Promise { assertIndySdkWallet(agentContext.wallet) + assertUnqualifiedProofRequest(options.proofRequest) try { // Open indy credential search diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts index 72abbbdea8..01973d31dd 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts @@ -12,14 +12,18 @@ import type { } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' -import { generateLegacyProverDidLikeString } from '@aries-framework/anoncreds' +import { parseIndyDid, getUnqualifiedSchemaId, 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 { + assertUnqualifiedCredentialDefinitionId, + assertUnqualifiedCredentialOffer, + assertUnqualifiedCredentialRequest, + assertUnqualifiedRevocationRegistryId, +} from '../utils/assertUnqualified' import { createTailsReader } from '../utils/tails' import { indySdkSchemaFromAnonCreds } from '../utils/transform' @@ -63,7 +67,7 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { const { namespaceIdentifier } = parseIndyDid(options.issuerId) // parse schema in a way that supports both unqualified and qualified identifiers - const legacySchemaId = getLegacySchemaId(namespaceIdentifier, schema.name, schema.version) + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, schema.name, schema.version) if (!metadata) throw new AriesFrameworkError('The metadata parameter is required when using Indy, but received undefined.') @@ -100,6 +104,8 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { options: CreateCredentialOfferOptions ): Promise { assertIndySdkWallet(agentContext.wallet) + assertUnqualifiedCredentialDefinitionId(options.credentialDefinitionId) + try { return await this.indySdk.issuerCreateCredentialOffer(agentContext.wallet.handle, options.credentialDefinitionId) } catch (error) { @@ -114,6 +120,12 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { const { tailsFilePath, credentialOffer, credentialRequest, credentialValues, revocationRegistryId } = options assertIndySdkWallet(agentContext.wallet) + assertUnqualifiedCredentialOffer(options.credentialOffer) + assertUnqualifiedCredentialRequest(options.credentialRequest) + if (options.revocationRegistryId) { + assertUnqualifiedRevocationRegistryId(options.revocationRegistryId) + } + try { // Indy SDK requires tailsReaderHandle. Use null if no tailsFilePath is present const tailsReaderHandle = tailsFilePath ? await createTailsReader(agentContext, tailsFilePath) : 0 diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts index b280256229..5d03e7e18c 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkVerifierService.ts @@ -2,11 +2,12 @@ import type { AnonCredsVerifierService, VerifyProofOptions } from '@aries-framew import type { AgentContext } from '@aries-framework/core' import type { CredentialDefs, Schemas, RevocRegDefs, RevRegs, IndyProofRequest, IndyProof } from 'indy-sdk' +import { parseIndyCredentialDefinitionId } from '@aries-framework/anoncreds' import { inject, injectable } from '@aries-framework/core' import { IndySdkError, isIndyError } from '../../error' import { IndySdk, IndySdkSymbol } from '../../types' -import { parseCredentialDefinitionId } from '../utils/identifiers' +import { assertAllUnqualified } from '../utils/assertUnqualified' import { indySdkCredentialDefinitionFromAnonCreds, indySdkRevocationRegistryDefinitionFromAnonCreds, @@ -23,6 +24,12 @@ export class IndySdkVerifierService implements AnonCredsVerifierService { } public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { + assertAllUnqualified({ + credentialDefinitionIds: Object.keys(options.credentialDefinitions), + schemaIds: Object.keys(options.schemas), + revocationRegistryIds: Object.keys(options.revocationRegistries), + }) + try { // The AnonCredsSchema doesn't contain the seqNo anymore. However, the indy credential definition id // does contain the seqNo, so we can extract it from the credential definition id. @@ -39,7 +46,7 @@ export class IndySdkVerifierService implements AnonCredsVerifierService { ) // Get the seqNo for the schemas so we can use it when transforming the schemas - const { schemaSeqNo } = parseCredentialDefinitionId(credentialDefinitionId) + const { schemaSeqNo } = parseIndyCredentialDefinitionId(credentialDefinitionId) seqNoMap[credentialDefinition.schemaId] = Number(schemaSeqNo) } diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/assertUnqualified.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/assertUnqualified.test.ts new file mode 100644 index 0000000000..3475cc48bc --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/assertUnqualified.test.ts @@ -0,0 +1,152 @@ +import type { AnonCredsCredentialOffer, AnonCredsCredentialRequest } from '@aries-framework/anoncreds' + +import { + assertUnqualifiedCredentialDefinitionId, + assertUnqualifiedCredentialOffer, + assertUnqualifiedCredentialRequest, + assertUnqualifiedIssuerId, + assertUnqualifiedProofRequest, + assertUnqualifiedRevocationRegistryId, + assertUnqualifiedSchemaId, +} from '../assertUnqualified' + +describe('assertUnqualified', () => { + describe('assertUnqualifiedCredentialDefinitionId', () => { + test('throws when a non-unqualified credential definition id is passed', () => { + expect(() => + assertUnqualifiedCredentialDefinitionId( + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID' + ) + ).toThrow() + }) + + test('does not throw when an unqualified credential definition id is passed', () => { + expect(() => + assertUnqualifiedCredentialDefinitionId('N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID') + ).not.toThrow() + }) + }) + + describe('assertUnqualifiedSchemaId', () => { + test('throws when a non-unqualified schema id is passed', () => { + expect(() => + assertUnqualifiedSchemaId('did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0') + ).toThrowError('Schema id') + }) + + test('does not throw when an unqualified schema id is passed', () => { + expect(() => assertUnqualifiedSchemaId('BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0')).not.toThrow() + }) + }) + + describe('assertUnqualifiedRevocationRegistryId', () => { + test('throws when a non-unqualified revocation registry id is passed', () => { + expect(() => + assertUnqualifiedRevocationRegistryId( + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/REV_REG_DEF/100669/SCH Employee ID/1-1024' + ) + ).toThrowError('Revocation registry id') + }) + + test('does not throw when an unqualified revocation registry id is passed', () => { + expect(() => + assertUnqualifiedRevocationRegistryId( + 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + ) + ).not.toThrow() + }) + }) + + describe('assertUnqualifiedIssuerId', () => { + test('throws when a non-unqualified issuer id is passed', () => { + expect(() => assertUnqualifiedIssuerId('did:indy:sovrin:N7baRMcyvPwWc8v85CtZ6e')).toThrowError('Issuer id') + }) + + test('does not throw when an unqualified issuer id is passed', () => { + expect(() => assertUnqualifiedIssuerId('N7baRMcyvPwWc8v85CtZ6e')).not.toThrow() + }) + }) + + describe('assertUnqualifiedCredentialOffer', () => { + test('throws when non-unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedCredentialOffer({ + cred_def_id: 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID', + schema_id: 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0', + } as AnonCredsCredentialOffer) + ).toThrowError('Credential definition id') + + expect(() => + assertUnqualifiedCredentialOffer({ + cred_def_id: 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID', + schema_id: 'did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0', + } as AnonCredsCredentialOffer) + ).toThrowError('Schema id') + }) + + test('does not throw when only unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedCredentialOffer({ + cred_def_id: 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID', + schema_id: 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0', + } as AnonCredsCredentialOffer) + ).not.toThrow() + }) + }) + + describe('assertUnqualifiedCredentialRequest', () => { + test('throws when non-unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedCredentialRequest({ + cred_def_id: 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID', + } as AnonCredsCredentialRequest) + ).toThrowError('Credential definition id') + }) + + test('does not throw when only unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedCredentialRequest({ + cred_def_id: 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID', + } as AnonCredsCredentialRequest) + ).not.toThrow() + }) + }) + + describe('assertUnqualifiedProofRequest', () => { + test('throws when non-unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedProofRequest({ + requested_attributes: { + a: { + restrictions: [ + { + cred_def_id: 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID', + }, + ], + }, + }, + requested_predicates: {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + ).toThrowError('Credential definition id') + }) + + test('does not throw when only unqualified identifiers are passed', () => { + expect(() => + assertUnqualifiedProofRequest({ + requested_attributes: { + a: { + restrictions: [ + { + schema_id: 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0', + }, + ], + }, + }, + requested_predicates: {}, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + } as any) + ).not.toThrow() + }) + }) +}) 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 f60ebe04a1..9b9a54ba83 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -2,13 +2,7 @@ import { getDidIndyCredentialDefinitionId, getDidIndyRevocationRegistryId, getDidIndySchemaId, - getLegacyCredentialDefinitionId, - getLegacyRevocationRegistryId, - getLegacySchemaId, indySdkAnonCredsRegistryIdentifierRegex, - parseCredentialDefinitionId, - parseRevocationRegistryId, - parseSchemaId, } from '../identifiers' describe('identifiers', () => { @@ -49,33 +43,6 @@ describe('identifiers', () => { }) }) - 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' @@ -109,77 +76,4 @@ describe('identifiers', () => { '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', - namespaceIdentifier: '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( - { - namespaceIdentifier: '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', - namespaceIdentifier: '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({ - namespaceIdentifier: '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', - namespaceIdentifier: '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', - namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', - did: 'did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9', - schemaSeqNo: '56495', - credentialDefinitionTag: 'npdb', - revocationRegistryTag: 'TAG1', - }) - }) - }) }) diff --git a/packages/indy-sdk/src/anoncreds/utils/assertUnqualified.ts b/packages/indy-sdk/src/anoncreds/utils/assertUnqualified.ts new file mode 100644 index 0000000000..320fadcb6e --- /dev/null +++ b/packages/indy-sdk/src/anoncreds/utils/assertUnqualified.ts @@ -0,0 +1,133 @@ +import type { + AnonCredsCredentialOffer, + AnonCredsCredentialRequest, + AnonCredsProofRequest, +} from '@aries-framework/anoncreds' + +import { + unqualifiedRevocationRegistryIdRegex, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedIndyDidRegex, + unqualifiedSchemaIdRegex, +} from '@aries-framework/anoncreds' +import { AriesFrameworkError } from '@aries-framework/core' + +/** + * Assert that a credential definition id is unqualified. + */ +export function assertUnqualifiedCredentialDefinitionId(credentialDefinitionId: string) { + if (!unqualifiedCredentialDefinitionIdRegex.test(credentialDefinitionId)) { + throw new AriesFrameworkError( + `Credential definition id '${credentialDefinitionId}' is not an unqualified credential definition id. Indy SDK only supports unqualified identifiers.` + ) + } +} + +/** + * Assert that a schema id is unqualified. + */ +export function assertUnqualifiedSchemaId(schemaId: string) { + if (!unqualifiedSchemaIdRegex.test(schemaId)) { + throw new AriesFrameworkError( + `Schema id '${schemaId}' is not an unqualified schema id. Indy SDK only supports unqualified identifiers.` + ) + } +} + +/** + * Assert that a revocation registry id is unqualified. + */ +export function assertUnqualifiedRevocationRegistryId(revocationRegistryId: string) { + if (!unqualifiedRevocationRegistryIdRegex.test(revocationRegistryId)) { + throw new AriesFrameworkError( + `Revocation registry id '${revocationRegistryId}' is not an unqualified revocation registry id. Indy SDK only supports unqualified identifiers.` + ) + } +} + +/** + * Assert that an issuer id is unqualified. + */ +export function assertUnqualifiedIssuerId(issuerId: string) { + if (!unqualifiedIndyDidRegex.test(issuerId)) { + throw new AriesFrameworkError( + `Issuer id '${issuerId}' is not an unqualified issuer id. Indy SDK only supports unqualified identifiers.` + ) + } +} + +/** + * Assert that a credential offer only contains unqualified identifiers. + */ +export function assertUnqualifiedCredentialOffer(credentialOffer: AnonCredsCredentialOffer) { + assertUnqualifiedCredentialDefinitionId(credentialOffer.cred_def_id) + assertUnqualifiedSchemaId(credentialOffer.schema_id) +} + +/** + * Assert that a credential request only contains unqualified identifiers. + */ +export function assertUnqualifiedCredentialRequest(credentialRequest: AnonCredsCredentialRequest) { + assertUnqualifiedCredentialDefinitionId(credentialRequest.cred_def_id) +} + +/** + * Assert that a proof request only contains unqualified identifiers. + */ +export function assertUnqualifiedProofRequest(proofRequest: AnonCredsProofRequest) { + const allRequested = [ + ...Object.values(proofRequest.requested_attributes), + ...Object.values(proofRequest.requested_predicates), + ] + + for (const requested of allRequested) { + for (const restriction of requested.restrictions ?? []) { + assertAllUnqualified({ + credentialDefinitionIds: [restriction.cred_def_id], + schemaIds: [restriction.schema_id], + revocationRegistryIds: [restriction.rev_reg_id], + issuerIds: [restriction.issuer_did, restriction.schema_issuer_did], + }) + } + } +} + +export function assertAllUnqualified({ + schemaIds = [], + credentialDefinitionIds = [], + revocationRegistryIds = [], + issuerIds = [], +}: { + schemaIds?: Array + credentialDefinitionIds?: Array + revocationRegistryIds?: Array + issuerIds?: Array +}) { + for (const schemaId of schemaIds) { + // We don't validate undefined values + if (!schemaId) continue + + assertUnqualifiedSchemaId(schemaId) + } + + for (const credentialDefinitionId of credentialDefinitionIds) { + // We don't validate undefined values + if (!credentialDefinitionId) continue + + assertUnqualifiedCredentialDefinitionId(credentialDefinitionId) + } + + for (const revocationRegistryId of revocationRegistryIds) { + // We don't validate undefined values + if (!revocationRegistryId) continue + + assertUnqualifiedRevocationRegistryId(revocationRegistryId) + } + + for (const issuerId of issuerIds) { + // We don't validate undefined values + if (!issuerId) continue + + assertUnqualifiedIssuerId(issuerId) + } +} diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index decd85f10b..4cedb11ff4 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -1,33 +1,17 @@ /** - * NOTE: this file is availalbe in both the indy-sdk and indy-vdr packages. If making changes to + * NOTE: this file is available 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:(.+)$/ +import { + unqualifiedSchemaIdRegex, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedRevocationRegistryIdRegex, + didIndyCredentialDefinitionIdRegex, + didIndyRevocationRegistryIdRegex, + didIndySchemaIdRegex, + didIndyRegex, +} from '@aries-framework/anoncreds' // combines both legacy and did:indy anoncreds identifiers and also the issuer id const indySdkAnonCredsRegexes = [ @@ -36,18 +20,18 @@ const indySdkAnonCredsRegexes = [ // 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, + didIndyRegex, // schema didIndySchemaIdRegex, - legacyIndySchemaIdRegex, + unqualifiedSchemaIdRegex, // credential definition didIndyCredentialDefinitionIdRegex, - legacyIndyCredentialDefinitionIdRegex, + unqualifiedCredentialDefinitionIdRegex, // revocation registry - legacyIndyRevocationRegistryIdRegex, + unqualifiedRevocationRegistryIdRegex, didIndyRevocationRegistryIdRegex, ] @@ -59,14 +43,6 @@ export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, na 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: string | number, tag: string) { - return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` -} - export function getDidIndyCredentialDefinitionId( namespace: string, unqualifiedDid: string, @@ -76,16 +52,6 @@ export function getDidIndyCredentialDefinitionId( 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, @@ -95,118 +61,3 @@ export function getDidIndyRevocationRegistryId( ) { return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` } - -interface ParsedSchemaId { - did: string - namespaceIdentifier: string - schemaName: string - schemaVersion: string - namespace?: string -} - -export function parseSchemaId(schemaId: string): ParsedSchemaId { - const didIndyMatch = schemaId.match(didIndySchemaIdRegex) - if (didIndyMatch) { - const [, did, namespace, namespaceIdentifier, schemaName, schemaVersion] = didIndyMatch - - return { - did, - namespaceIdentifier, - schemaName, - schemaVersion, - namespace, - } - } - - const legacyMatch = schemaId.match(legacyIndySchemaIdRegex) - if (legacyMatch) { - const [, did, schemaName, schemaVersion] = legacyMatch - - return { - did, - namespaceIdentifier: did, - schemaName, - schemaVersion, - } - } - - throw new Error(`Invalid schema id: ${schemaId}`) -} - -interface ParsedCredentialDefinitionId { - did: string - namespaceIdentifier: string - schemaSeqNo: string - tag: string - namespace?: string -} - -export function parseCredentialDefinitionId(credentialDefinitionId: string): ParsedCredentialDefinitionId { - const didIndyMatch = credentialDefinitionId.match(didIndyCredentialDefinitionIdRegex) - if (didIndyMatch) { - const [, did, namespace, namespaceIdentifier, schemaSeqNo, tag] = didIndyMatch - - return { - did, - namespaceIdentifier, - schemaSeqNo, - tag, - namespace, - } - } - - const legacyMatch = credentialDefinitionId.match(legacyIndyCredentialDefinitionIdRegex) - if (legacyMatch) { - const [, did, schemaSeqNo, tag] = legacyMatch - - return { - did, - namespaceIdentifier: did, - schemaSeqNo, - tag, - } - } - - throw new Error(`Invalid credential definition id: ${credentialDefinitionId}`) -} - -interface ParsedRevocationRegistryId { - did: string - namespaceIdentifier: string - schemaSeqNo: string - credentialDefinitionTag: string - revocationRegistryTag: string - namespace?: string -} - -export function parseRevocationRegistryId(revocationRegistryId: string): ParsedRevocationRegistryId { - const didIndyMatch = revocationRegistryId.match(didIndyRevocationRegistryIdRegex) - if (didIndyMatch) { - const [, did, namespace, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag] = - didIndyMatch - - return { - did, - namespaceIdentifier, - schemaSeqNo, - credentialDefinitionTag, - revocationRegistryTag, - namespace, - } - } - - const legacyMatch = revocationRegistryId.match(legacyIndyRevocationRegistryIdRegex) - if (legacyMatch) { - const [, did, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag] = legacyMatch - - return { - did, - namespaceIdentifier: did, - schemaSeqNo, - credentialDefinitionTag, - revocationRegistryTag, - } - } - - throw new Error(`Invalid revocation registry id: ${revocationRegistryId}`) -} diff --git a/packages/indy-sdk/src/anoncreds/utils/transform.ts b/packages/indy-sdk/src/anoncreds/utils/transform.ts index 9ddc18f81d..73b5441c93 100644 --- a/packages/indy-sdk/src/anoncreds/utils/transform.ts +++ b/packages/indy-sdk/src/anoncreds/utils/transform.ts @@ -8,10 +8,10 @@ import type { } from '@aries-framework/anoncreds' import type { CredDef, CredReqMetadata, RevocReg, RevocRegDef, RevocRegDelta, Schema } from 'indy-sdk' -import { parseCredentialDefinitionId, parseSchemaId } from './identifiers' +import { parseIndyCredentialDefinitionId, parseIndySchemaId } from '@aries-framework/anoncreds' export function anonCredsSchemaFromIndySdk(schema: Schema): AnonCredsSchema { - const { did } = parseSchemaId(schema.id) + const { did } = parseIndySchemaId(schema.id) return { issuerId: did, name: schema.name, @@ -32,7 +32,7 @@ export function indySdkSchemaFromAnonCreds(schemaId: string, schema: AnonCredsSc } export function anonCredsCredentialDefinitionFromIndySdk(credentialDefinition: CredDef): AnonCredsCredentialDefinition { - const { did } = parseCredentialDefinitionId(credentialDefinition.id) + const { did } = parseIndyCredentialDefinitionId(credentialDefinition.id) return { issuerId: did, diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts index a7aba8eab1..2a3c6c3097 100644 --- a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts @@ -12,6 +12,7 @@ import type { } from '@aries-framework/core' import type { NymRole } from 'indy-sdk' +import { parseIndyDid } from '@aries-framework/anoncreds' import { DidDocumentRole, DidRecord, DidRepository, KeyType, Key } from '@aries-framework/core' import { IndySdkError } from '../error' @@ -21,7 +22,7 @@ import { IndySdkSymbol } from '../types' import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' import { isLegacySelfCertifiedDid, legacyIndyDidFromPublicKeyBase58 } from '../utils/did' -import { createKeyAgreementKey, indyDidDocumentFromDid, parseIndyDid, verificationKeyForIndyDid } from './didIndyUtil' +import { createKeyAgreementKey, indyDidDocumentFromDid, verificationKeyForIndyDid } from './didIndyUtil' import { addServicesFromEndpointsAttrib } from './didSovUtil' export class IndySdkIndyDidRegistrar implements DidRegistrar { diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts index 4aa0ddf1d3..1c486eb3aa 100644 --- a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts @@ -3,12 +3,14 @@ import type { IndySdkPool } from '../ledger' import type { IndySdk } from '../types' import type { DidResolutionResult, DidResolver, AgentContext } from '@aries-framework/core' +import { parseIndyDid } from '@aries-framework/anoncreds' + 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 { createKeyAgreementKey, indyDidDocumentFromDid } from './didIndyUtil' import { addServicesFromEndpointsAttrib } from './didSovUtil' export class IndySdkIndyDidResolver implements DidResolver { diff --git a/packages/indy-sdk/src/dids/didIndyUtil.ts b/packages/indy-sdk/src/dids/didIndyUtil.ts index 928ae1007e..7da10664b2 100644 --- a/packages/indy-sdk/src/dids/didIndyUtil.ts +++ b/packages/indy-sdk/src/dids/didIndyUtil.ts @@ -9,18 +9,6 @@ import { 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, namespaceIdentifier] = match - return { namespace, namespaceIdentifier } - } 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` diff --git a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts index d66251a83c..bf632152bb 100644 --- a/packages/indy-sdk/src/ledger/IndySdkPoolService.ts +++ b/packages/indy-sdk/src/ledger/IndySdkPoolService.ts @@ -3,6 +3,7 @@ import type { IndySdk } from '../types' import type { AgentContext, Key } from '@aries-framework/core' import type { GetNymResponse, LedgerReadReplyResponse, LedgerRequest, LedgerWriteReplyResponse } from 'indy-sdk' +import { didIndyRegex } from '@aries-framework/anoncreds' import { TypedArrayEncoder, CacheModuleConfig, @@ -17,7 +18,7 @@ import { Subject } from 'rxjs' import { IndySdkModuleConfig } from '../IndySdkModuleConfig' import { IndySdkError, isIndyError } from '../error' import { assertIndySdkWallet } from '../utils/assertIndySdkWallet' -import { DID_INDY_REGEX, isLegacySelfCertifiedDid } from '../utils/did' +import { isLegacySelfCertifiedDid } from '../utils/did' import { allSettled, onlyFulfilled, onlyRejected } from '../utils/promises' import { IndySdkPool } from './IndySdkPool' @@ -62,7 +63,7 @@ export class IndySdkPoolService { * 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 + * either now or in the past. The nymResponse can be used to prevent multiple ledger queries fetching the same * did */ public async getPoolForDid( @@ -70,7 +71,7 @@ export class IndySdkPoolService { did: string ): Promise<{ pool: IndySdkPool; nymResponse?: GetNymResponse }> { // Check if the did starts with did:indy - const match = did.match(DID_INDY_REGEX) + const match = did.match(didIndyRegex) if (match) { const [, namespace] = match diff --git a/packages/indy-sdk/src/utils/did.ts b/packages/indy-sdk/src/utils/did.ts index 7d78cd09e2..afb080696f 100644 --- a/packages/indy-sdk/src/utils/did.ts +++ b/packages/indy-sdk/src/utils/did.ts @@ -19,7 +19,6 @@ 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 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 fd33a35696..d4c2af8e38 100644 --- a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -1,9 +1,9 @@ import type { IndySdkIndyDidCreateOptions } from '../src' +import { parseIndyDid } from '@aries-framework/anoncreds' 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' diff --git a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts index 433f9d0ae5..01cdd18449 100644 --- a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts +++ b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts @@ -12,6 +12,15 @@ import type { } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' +import { + getUnqualifiedCredentialDefinitionId, + getUnqualifiedRevocationRegistryId, + getUnqualifiedSchemaId, + parseIndyCredentialDefinitionId, + parseIndyDid, + parseIndyRevocationRegistryId, + parseIndySchemaId, +} from '@aries-framework/anoncreds' import { GetSchemaRequest, SchemaRequest, @@ -22,19 +31,13 @@ import { GetRevocationRegistryDefinitionRequest, } from '@hyperledger/indy-vdr-shared' -import { parseIndyDid, verificationKeyForIndyDid } from '../dids/didIndyUtil' +import { verificationKeyForIndyDid } from '../dids/didIndyUtil' import { IndyVdrPoolService } from '../pool' import { - getLegacySchemaId, - getLegacyCredentialDefinitionId, indyVdrAnonCredsRegistryIdentifierRegex, - parseSchemaId, getDidIndySchemaId, - parseCredentialDefinitionId, getDidIndyCredentialDefinitionId, - parseRevocationRegistryId, - getLegacyRevocationRegistryId, } from './utils/identifiers' import { anonCredsRevocationStatusListFromIndyVdr } from './utils/transform' @@ -48,12 +51,12 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) // parse schema id (supports did:indy and legacy) - const { did, namespaceIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) + const { did, namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(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(namespaceIdentifier, schemaName, schemaVersion) + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) const request = new GetSchemaRequest({ schemaId: legacySchemaId }) agentContext.config.logger.trace( @@ -133,7 +136,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { options.schema.name, options.schema.version ) - const legacySchemaId = getLegacySchemaId(namespaceIdentifier, options.schema.name, options.schema.version) + const legacySchemaId = getUnqualifiedSchemaId(namespaceIdentifier, options.schema.name, options.schema.version) const schemaRequest = new SchemaRequest({ submitterDid: namespaceIdentifier, @@ -198,14 +201,14 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) // we support did:indy and legacy identifiers - const { did, namespaceIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) + const { did, namespaceIdentifier, schemaSeqNo, tag } = parseIndyCredentialDefinitionId(credentialDefinitionId) const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Getting credential definition '${credentialDefinitionId}' from ledger '${pool.indyNamespace}'` ) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) const request = new GetCredentialDefinitionRequest({ credentialDefinitionId: legacyCredentialDefinitionId, }) @@ -304,7 +307,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } } - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( + const legacyCredentialDefinitionId = getUnqualifiedCredentialDefinitionId( options.credentialDefinition.issuerId, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag @@ -377,14 +380,14 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) const { did, namespaceIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = - parseRevocationRegistryId(revocationRegistryDefinitionId) + parseIndyRevocationRegistryId(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( + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, @@ -431,7 +434,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { schemaSeqNo, credentialDefinitionTag ) - : getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) + : getUnqualifiedCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) const revocationRegistryDefinition = { issuerId: did, @@ -488,14 +491,14 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = - parseRevocationRegistryId(revocationRegistryId) + parseIndyRevocationRegistryId(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( + const legacyRevocationRegistryId = getUnqualifiedRevocationRegistryId( namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, @@ -602,7 +605,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { const schema = response.result.data?.txn.data as SchemaType - const schemaId = getLegacySchemaId(did, schema.data.name, schema.data.version) + const schemaId = getUnqualifiedSchemaId(did, schema.data.name, schema.data.version) return { schema: { 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..b96720611b --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -0,0 +1,79 @@ +import { + getDidIndyCredentialDefinitionId, + getDidIndyRevocationRegistryId, + getDidIndySchemaId, + indyVdrAnonCredsRegistryIdentifierRegex, +} from '../identifiers' + +describe('identifiers', () => { + describe('indyVdrAnonCredsRegistryIdentifierRegex', () => { + test('matches against a legacy 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('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' + ) + }) +}) diff --git a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts deleted file mode 100644 index 1f01c18209..0000000000 --- a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts +++ /dev/null @@ -1,185 +0,0 @@ -import { - getDidIndyCredentialDefinitionId, - getDidIndyRevocationRegistryId, - getDidIndySchemaId, - getLegacyCredentialDefinitionId, - getLegacyRevocationRegistryId, - getLegacySchemaId, - indyVdrAnonCredsRegistryIdentifierRegex, - parseCredentialDefinitionId, - parseRevocationRegistryId, - parseSchemaId, -} from '../identifiers' - -describe('identifiers', () => { - describe('indyVdrAnonCredsRegistryIdentifierRegex', () => { - test('matches against a legacy 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', - namespaceIdentifier: '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( - { - namespaceIdentifier: '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', - namespaceIdentifier: '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({ - namespaceIdentifier: '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', - namespaceIdentifier: '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', - namespaceIdentifier: '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 cc05d2b3bb..1e9d6a8fd3 100644 --- a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts @@ -3,31 +3,15 @@ * 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:(.+)$/ +import { + unqualifiedSchemaIdRegex, + unqualifiedCredentialDefinitionIdRegex, + unqualifiedRevocationRegistryIdRegex, + didIndyCredentialDefinitionIdRegex, + didIndyRevocationRegistryIdRegex, + didIndySchemaIdRegex, + didIndyRegex, +} from '@aries-framework/anoncreds' // combines both legacy and did:indy anoncreds identifiers and also the issuer id const indyVdrAnonCredsRegexes = [ @@ -36,18 +20,18 @@ const indyVdrAnonCredsRegexes = [ // 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, + didIndyRegex, // schema didIndySchemaIdRegex, - legacyIndySchemaIdRegex, + unqualifiedSchemaIdRegex, // credential definition didIndyCredentialDefinitionIdRegex, - legacyIndyCredentialDefinitionIdRegex, + unqualifiedCredentialDefinitionIdRegex, // revocation registry - legacyIndyRevocationRegistryIdRegex, + unqualifiedRevocationRegistryIdRegex, didIndyRevocationRegistryIdRegex, ] @@ -59,14 +43,6 @@ export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, na 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: string | number, tag: string) { - return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` -} - export function getDidIndyCredentialDefinitionId( namespace: string, unqualifiedDid: string, @@ -76,16 +52,6 @@ export function getDidIndyCredentialDefinitionId( 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, @@ -95,118 +61,3 @@ export function getDidIndyRevocationRegistryId( ) { return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` } - -interface ParsedSchemaId { - did: string - namespaceIdentifier: string - schemaName: string - schemaVersion: string - namespace?: string -} - -export function parseSchemaId(schemaId: string): ParsedSchemaId { - const didIndyMatch = schemaId.match(didIndySchemaIdRegex) - if (didIndyMatch) { - const [, did, namespace, namespaceIdentifier, schemaName, schemaVersion] = didIndyMatch - - return { - did, - namespaceIdentifier, - schemaName, - schemaVersion, - namespace, - } - } - - const legacyMatch = schemaId.match(legacyIndySchemaIdRegex) - if (legacyMatch) { - const [, did, schemaName, schemaVersion] = legacyMatch - - return { - did, - namespaceIdentifier: did, - schemaName, - schemaVersion, - } - } - - throw new Error(`Invalid schema id: ${schemaId}`) -} - -interface ParsedCredentialDefinitionId { - did: string - namespaceIdentifier: string - schemaSeqNo: string - tag: string - namespace?: string -} - -export function parseCredentialDefinitionId(credentialDefinitionId: string): ParsedCredentialDefinitionId { - const didIndyMatch = credentialDefinitionId.match(didIndyCredentialDefinitionIdRegex) - if (didIndyMatch) { - const [, did, namespace, namespaceIdentifier, schemaSeqNo, tag] = didIndyMatch - - return { - did, - namespaceIdentifier, - schemaSeqNo, - tag, - namespace, - } - } - - const legacyMatch = credentialDefinitionId.match(legacyIndyCredentialDefinitionIdRegex) - if (legacyMatch) { - const [, did, schemaSeqNo, tag] = legacyMatch - - return { - did, - namespaceIdentifier: did, - schemaSeqNo, - tag, - } - } - - throw new Error(`Invalid credential definition id: ${credentialDefinitionId}`) -} - -interface ParsedRevocationRegistryId { - did: string - namespaceIdentifier: string - schemaSeqNo: string - credentialDefinitionTag: string - revocationRegistryTag: string - namespace?: string -} - -export function parseRevocationRegistryId(revocationRegistryId: string): ParsedRevocationRegistryId { - const didIndyMatch = revocationRegistryId.match(didIndyRevocationRegistryIdRegex) - if (didIndyMatch) { - const [, did, namespace, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag] = - didIndyMatch - - return { - did, - namespaceIdentifier, - schemaSeqNo, - credentialDefinitionTag, - revocationRegistryTag, - namespace, - } - } - - const legacyMatch = revocationRegistryId.match(legacyIndyRevocationRegistryIdRegex) - if (legacyMatch) { - const [, did, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag] = legacyMatch - - return { - did, - namespaceIdentifier: did, - schemaSeqNo, - credentialDefinitionTag, - revocationRegistryTag, - } - } - - throw new Error(`Invalid revocation registry id: ${revocationRegistryId}`) -} diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts index ffcc98b929..33a0e088a9 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts @@ -11,6 +11,7 @@ import type { DidDocumentService, } from '@aries-framework/core' +import { parseIndyDid } from '@aries-framework/anoncreds' import { IndyAgentService, DidCommV1Service, @@ -32,7 +33,6 @@ import { createKeyAgreementKey, didDocDiff, indyDidDocumentFromDid, - parseIndyDid, isSelfCertifiedIndyDid, verificationKeyForIndyDid, } from './didIndyUtil' diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts index 124e5da88e..ddc339c745 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts @@ -2,12 +2,13 @@ import type { GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' import type { IndyVdrPool } from '../pool' import type { DidResolutionResult, DidResolver, AgentContext } from '@aries-framework/core' +import { parseIndyDid } from '@aries-framework/anoncreds' import { GetAttribRequest, GetNymRequest } from '@hyperledger/indy-vdr-shared' import { IndyVdrError, IndyVdrNotFoundError } from '../error' import { IndyVdrPoolService } from '../pool' -import { combineDidDocumentWithJson, createKeyAgreementKey, indyDidDocumentFromDid, parseIndyDid } from './didIndyUtil' +import { combineDidDocumentWithJson, createKeyAgreementKey, indyDidDocumentFromDid } from './didIndyUtil' import { getFullVerkey, addServicesFromEndpointsAttrib } from './didSovUtil' export class IndyVdrIndyDidResolver implements DidResolver { diff --git a/packages/indy-vdr/src/dids/didIndyUtil.ts b/packages/indy-vdr/src/dids/didIndyUtil.ts index e91f5ebd2b..50adce5226 100644 --- a/packages/indy-vdr/src/dids/didIndyUtil.ts +++ b/packages/indy-vdr/src/dids/didIndyUtil.ts @@ -1,5 +1,6 @@ import type { AgentContext } from '@aries-framework/core' +import { parseIndyDid } from '@aries-framework/anoncreds' import { getKeyFromVerificationMethod, AriesFrameworkError, @@ -14,8 +15,6 @@ import { TypedArrayEncoder, } from '@aries-framework/core' -import { DID_INDY_REGEX } from '../utils/did' - // Create a base DIDDoc template according to https://hyperledger.github.io/indy-did-method/#base-diddoc-template export function indyDidDocumentFromDid(did: string, verKeyBase58: string) { const verificationMethodId = `${did}#verkey` @@ -39,16 +38,6 @@ export function createKeyAgreementKey(verkey: string) { return TypedArrayEncoder.toBase58(convertPublicKeyToX25519(TypedArrayEncoder.fromBase58(verkey))) } -export function parseIndyDid(did: string) { - const match = did.match(DID_INDY_REGEX) - if (match) { - const [, namespace, namespaceIdentifier] = match - return { namespace, namespaceIdentifier } - } else { - throw new AriesFrameworkError(`${did} is not a valid did:indy did`) - } -} - const deepMerge = (a: Record, b: Record) => { const output: Record = {} diff --git a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts index 69ee1026a0..fc05f63672 100644 --- a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts +++ b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts @@ -1,12 +1,13 @@ import type { AgentContext } from '@aries-framework/core' import type { GetNymResponse } from '@hyperledger/indy-vdr-shared' +import { didIndyRegex } from '@aries-framework/anoncreds' import { Logger, InjectionSymbols, injectable, inject, CacheModuleConfig } from '@aries-framework/core' import { GetNymRequest } from '@hyperledger/indy-vdr-shared' import { IndyVdrModuleConfig } from '../IndyVdrModuleConfig' import { IndyVdrError, IndyVdrNotFoundError, IndyVdrNotConfiguredError } from '../error' -import { isSelfCertifiedDid, DID_INDY_REGEX } from '../utils/did' +import { isSelfCertifiedDid } from '../utils/did' import { allSettled, onlyFulfilled, onlyRejected } from '../utils/promises' import { IndyVdrPool } from './IndyVdrPool' @@ -46,7 +47,7 @@ export class IndyVdrPoolService { did: string ): Promise<{ pool: IndyVdrPool; nymResponse?: CachedDidResponse['nymResponse'] }> { // Check if the did starts with did:indy - const match = did.match(DID_INDY_REGEX) + const match = did.match(didIndyRegex) if (match) { const [, namespace] = match diff --git a/packages/indy-vdr/src/utils/did.ts b/packages/indy-vdr/src/utils/did.ts index 44632246bb..f3f346f070 100644 --- a/packages/indy-vdr/src/utils/did.ts +++ b/packages/indy-vdr/src/utils/did.ts @@ -17,7 +17,6 @@ import { TypedArrayEncoder } from '@aries-framework/core' -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})$/ export const ABBREVIATED_VERKEY_REGEX = /^~[1-9A-HJ-NP-Za-km-z]{21,22}$/ /** 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 dbf68311d0..2e8035fed0 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,5 +1,6 @@ import type { IndyVdrDidCreateOptions } from '../src/dids/IndyVdrIndyDidRegistrar' +import { didIndyRegex } from '@aries-framework/anoncreds' import { Key, JsonTransformer, @@ -22,7 +23,6 @@ import { IndyVdrModule, IndyVdrSovDidResolver } from '../src' import { IndyVdrIndyDidRegistrar } from '../src/dids/IndyVdrIndyDidRegistrar' import { IndyVdrIndyDidResolver } from '../src/dids/IndyVdrIndyDidResolver' import { indyDidFromNamespaceAndInitialKey } from '../src/dids/didIndyUtil' -import { DID_INDY_REGEX } from '../src/utils/did' import { indyVdrModuleConfig } from './helpers' @@ -76,16 +76,16 @@ describe('Indy VDR Indy Did Registrar', () => { didRegistrationMetadata: {}, didState: { state: 'finished', - did: expect.stringMatching(DID_INDY_REGEX), + did: expect.stringMatching(didIndyRegex), didDocument: { '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], - id: expect.stringMatching(DID_INDY_REGEX), + id: expect.stringMatching(didIndyRegex), alsoKnownAs: undefined, controller: undefined, verificationMethod: [ { type: 'Ed25519VerificationKey2018', - controller: expect.stringMatching(DID_INDY_REGEX), + controller: expect.stringMatching(didIndyRegex), id: expect.stringContaining('#verkey'), publicKeyBase58: expect.any(String), }, 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 14b2a7e202..cc987e7888 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 @@ -1,3 +1,4 @@ +import { parseIndyDid } from '@aries-framework/anoncreds' import { DidsModule, Agent, TypedArrayEncoder, JsonTransformer } from '@aries-framework/core' import { indyVdr } from '@hyperledger/indy-vdr-nodejs' @@ -6,7 +7,6 @@ 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 { parseIndyDid } from '../src/dids/didIndyUtil' import { createDidOnLedger, indyVdrModuleConfig } from './helpers'