diff --git a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts index 4606899650..af5686ad5d 100644 --- a/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts +++ b/packages/anoncreds/src/formats/__tests__/legacy-indy-format-services.test.ts @@ -163,10 +163,10 @@ describe('Legacy indy format services', () => { ] const cd = parseCredentialDefinitionId(credentialDefinitionState.credentialDefinitionId) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.didIdentifier, cd.schemaSeqNo, cd.tag) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.namespaceIdentifier, cd.schemaSeqNo, cd.tag) const s = parseSchemaId(schemaState.schemaId) - const legacySchemaId = getLegacySchemaId(s.didIdentifier, s.schemaName, s.schemaVersion) + const legacySchemaId = getLegacySchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) // Holder creates proposal holderCredentialRecord.credentialAttributes = credentialAttributes diff --git a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts index abd3ddd6ed..5c2fc9954a 100644 --- a/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts +++ b/packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts @@ -64,7 +64,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { const parsed = parseSchemaId(schemaId) - const legacySchemaId = getLegacySchemaId(parsed.didIdentifier, parsed.schemaName, parsed.schemaVersion) + const legacySchemaId = getLegacySchemaId(parsed.namespaceIdentifier, parsed.schemaName, parsed.schemaVersion) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) if (!schema) { @@ -94,16 +94,21 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { agentContext: AgentContext, options: RegisterSchemaOptions ): Promise { - const { id: didIdentifier, namespace } = parseIndyDid(options.schema.issuerId) - const didIndySchemaId = getDidIndySchemaId(namespace, didIdentifier, options.schema.name, options.schema.version) - const legacySchemaId = getLegacySchemaId(didIdentifier, options.schema.name, options.schema.version) + const { namespaceIdentifier, namespace } = parseIndyDid(options.schema.issuerId) + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, options.schema.name, options.schema.version) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) this.schemas[didIndySchemaId] = options.schema this.schemas[legacySchemaId] = { ...options.schema, - issuerId: didIdentifier, + issuerId: namespaceIdentifier, } return { @@ -152,22 +157,22 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { ): Promise { const parsedSchema = parseSchemaId(options.credentialDefinition.schemaId) const legacySchemaId = getLegacySchemaId( - parsedSchema.didIdentifier, + parsedSchema.namespaceIdentifier, parsedSchema.schemaName, parsedSchema.schemaVersion ) const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId) - const { id: didIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( namespace, - didIdentifier, + namespaceIdentifier, indyLedgerSeqNo, options.credentialDefinition.tag ) const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( - didIdentifier, + namespaceIdentifier, indyLedgerSeqNo, options.credentialDefinition.tag ) @@ -175,7 +180,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry { this.credentialDefinitions[didIndyCredentialDefinitionId] = options.credentialDefinition this.credentialDefinitions[legacyCredentialDefinitionId] = { ...options.credentialDefinition, - issuerId: didIdentifier, + issuerId: namespaceIdentifier, schemaId: legacySchemaId, } diff --git a/packages/anoncreds/tests/legacyAnonCredsSetup.ts b/packages/anoncreds/tests/legacyAnonCredsSetup.ts index 5895e5c075..d0e40a33a6 100644 --- a/packages/anoncreds/tests/legacyAnonCredsSetup.ts +++ b/packages/anoncreds/tests/legacyAnonCredsSetup.ts @@ -60,7 +60,13 @@ import { parseSchemaId, } from '../../indy-sdk/src/anoncreds/utils/identifiers' import { getIndySdkModuleConfig } from '../../indy-sdk/tests/setupIndySdkModule' -import { IndyVdrAnonCredsRegistry, IndyVdrSovDidResolver, IndyVdrModule } from '../../indy-vdr/src' +import { + IndyVdrAnonCredsRegistry, + IndyVdrSovDidResolver, + IndyVdrModule, + IndyVdrIndyDidResolver, + IndyVdrIndyDidRegistrar, +} from '../../indy-vdr/src' import { V1CredentialProtocol, V1ProofProtocol, @@ -163,7 +169,8 @@ export const getAskarAnonCredsIndyModules = ({ networks: [indyNetworkConfig], }), dids: new DidsModule({ - resolvers: [new IndyVdrSovDidResolver()], // TODO: Support Registrar for tests + resolvers: [new IndyVdrSovDidResolver(), new IndyVdrIndyDidResolver()], + registrars: [new IndyVdrIndyDidRegistrar()], }), askar: new AskarModule(), cache: new CacheModule({ @@ -474,8 +481,8 @@ export async function prepareForAnonCredsIssuance(agent: Agent, { attributeNames const s = parseSchemaId(schema.schemaId) const cd = parseCredentialDefinitionId(credentialDefinition.credentialDefinitionId) - const legacySchemaId = getLegacySchemaId(s.didIdentifier, s.schemaName, s.schemaVersion) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.didIdentifier, cd.schemaSeqNo, cd.tag) + const legacySchemaId = getLegacySchemaId(s.namespaceIdentifier, s.schemaName, s.schemaVersion) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(cd.namespaceIdentifier, cd.schemaSeqNo, cd.tag) // Wait some time pass to let ledger settle the object await sleep(1000) diff --git a/packages/core/tests/helpers.ts b/packages/core/tests/helpers.ts index 2d11ae4109..034c046aef 100644 --- a/packages/core/tests/helpers.ts +++ b/packages/core/tests/helpers.ts @@ -133,7 +133,7 @@ export async function importExistingIndyDidFromPrivateKey(agent: Agent, privateK const unqualifiedIndyDid = TypedArrayEncoder.toBase58(key.publicKey.slice(0, 16)) // import the did in the wallet so it can be used - await agent.dids.import({ did: `did:sov:${unqualifiedIndyDid}` }) + await agent.dids.import({ did: `did:indy:pool:localtest:${unqualifiedIndyDid}` }) return unqualifiedIndyDid } diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts index fe0d30e35b..8c222b1a18 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkAnonCredsRegistry.ts @@ -31,12 +31,9 @@ import { } from '../utils/identifiers' import { anonCredsRevocationStatusListFromIndySdk } from '../utils/transform' -/** - * TODO: validation of the identifiers. The Indy SDK classes only support the legacy (unqualified) identifiers. - */ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { /** - * This class only supports resolving and registering objects with legacy indy identifiers. + * This class supports resolving and registering objects with did:indy as well as legacy indy identifiers. * It needs to include support for the schema, credential definition, revocation registry as well * as the issuer id (which is needed when registering objects). */ @@ -48,12 +45,12 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) // parse schema id (supports did:indy and legacy) - const { did, didIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) + const { did, namespaceIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.didIndyNamespace}'`) // even though we support did:indy and legacy identifiers we always need to fetch using the legacy identifier - const legacySchemaId = getLegacySchemaId(didIdentifier, schemaName, schemaVersion) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, schemaName, schemaVersion) const request = await indySdk.buildGetSchemaRequest(null, legacySchemaId) agentContext.config.logger.trace( @@ -110,7 +107,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { try { // This will throw an error if trying to register a schema with a legacy indy identifier. We only support did:indy identifiers // for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. - const { id: unqualifiedDid, namespace } = parseIndyDid(options.schema.issuerId) + const { namespaceIdentifier, namespace } = parseIndyDid(options.schema.issuerId) const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) @@ -121,8 +118,13 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { options.schema ) - const didIndySchemaId = getDidIndySchemaId(namespace, unqualifiedDid, options.schema.name, options.schema.version) - const legacySchemaId = getLegacySchemaId(unqualifiedDid, options.schema.name, options.schema.version) + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, options.schema.name, options.schema.version) const schema = { attrNames: options.schema.attrNames, @@ -134,7 +136,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { // buildSchemaRequest (seqNo is not yet known) } as IndySdkSchema - const request = await indySdk.buildSchemaRequest(unqualifiedDid, schema) + const request = await indySdk.buildSchemaRequest(namespaceIdentifier, schema) const submitterKey = await verificationKeyForIndyDid(agentContext, options.schema.issuerId) const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterKey) @@ -189,14 +191,14 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) // we support did:indy and legacy identifiers - const { did, didIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) + const { did, namespaceIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.didIndyNamespace}' to retrieve credential definition '${credentialDefinitionId}'` ) - const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, tag) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) const request = await indySdk.buildGetCredDefRequest(null, legacyCredentialDefinitionId) agentContext.config.logger.trace( @@ -224,7 +226,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { // Format the schema id based on the type of the credential definition id const schemaId = credentialDefinitionId.startsWith('did:indy') - ? getDidIndySchemaId(pool.didIndyNamespace, didIdentifier, schema.name, schema.version) + ? getDidIndySchemaId(pool.didIndyNamespace, namespaceIdentifier, schema.name, schema.version) : schema.schemaId return { @@ -279,7 +281,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { try { // This will throw an error if trying to register a credential defintion with a legacy indy identifier. We only support did:indy // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. - const { id: unqualifiedDid, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) @@ -312,18 +314,18 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { } const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( - unqualifiedDid, + namespaceIdentifier, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag ) const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( namespace, - unqualifiedDid, + namespaceIdentifier, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag ) - const request = await indySdk.buildCredDefRequest(unqualifiedDid, { + const request = await indySdk.buildCredDefRequest(namespaceIdentifier, { id: legacyCredentialDefinitionId, // Indy ledger requires the credential schemaId to be a string of the schema seqNo. schemaId: schemaMetadata.indyLedgerSeqNo.toString(), @@ -334,7 +336,6 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { }) const submitterKey = await verificationKeyForIndyDid(agentContext, options.credentialDefinition.issuerId) - const response = await indySdkPoolService.submitWriteRequest(agentContext, pool, request, submitterKey) agentContext.config.logger.debug( @@ -376,7 +377,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const { did, didIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = + const { did, namespaceIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = parseRevocationRegistryId(revocationRegistryDefinitionId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) @@ -385,7 +386,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ) const legacyRevocationRegistryId = getLegacyRevocationRegistryId( - didIdentifier, + namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag @@ -413,8 +414,13 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ) const credentialDefinitionId = revocationRegistryDefinitionId.startsWith('did:indy:') - ? getDidIndyCredentialDefinitionId(pool.didIndyNamespace, didIdentifier, schemaSeqNo, credentialDefinitionTag) - : getLegacyCredentialDefinitionId(didIdentifier, schemaSeqNo, credentialDefinitionTag) + ? getDidIndyCredentialDefinitionId( + pool.didIndyNamespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag + ) + : getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) return { resolutionMetadata: {}, @@ -465,7 +471,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) - const { did, didIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = parseRevocationRegistryId(revocationRegistryId) const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) @@ -474,7 +480,7 @@ export class IndySdkAnonCredsRegistry implements AnonCredsRegistry { ) const legacyRevocationRegistryId = getLegacyRevocationRegistryId( - didIdentifier, + namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts index 2b5365522b..72abbbdea8 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkIssuerService.ts @@ -33,13 +33,13 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { public async createSchema(agentContext: AgentContext, options: CreateSchemaOptions): Promise { // We only support passing qualified did:indy issuer ids in the indy issuer service when creating objects - const { id: unqualifiedDid } = parseIndyDid(options.issuerId) + const { namespaceIdentifier } = parseIndyDid(options.issuerId) const { name, version, attrNames, issuerId } = options assertIndySdkWallet(agentContext.wallet) try { - const [, schema] = await this.indySdk.issuerCreateSchema(unqualifiedDid, name, version, attrNames) + const [, schema] = await this.indySdk.issuerCreateSchema(namespaceIdentifier, name, version, attrNames) return { issuerId, @@ -60,10 +60,10 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { const { tag, supportRevocation, schema, issuerId, schemaId } = options // We only support passing qualified did:indy issuer ids in the indy issuer service when creating objects - const { id: unqualifiedDid } = parseIndyDid(options.issuerId) + const { namespaceIdentifier } = parseIndyDid(options.issuerId) // parse schema in a way that supports both unqualified and qualified identifiers - const legacySchemaId = getLegacySchemaId(unqualifiedDid, schema.name, schema.version) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, schema.name, schema.version) if (!metadata) throw new AriesFrameworkError('The metadata parameter is required when using Indy, but received undefined.') @@ -72,7 +72,7 @@ export class IndySdkIssuerService implements AnonCredsIssuerService { assertIndySdkWallet(agentContext.wallet) const [, credentialDefinition] = await this.indySdk.issuerCreateAndStoreCredentialDef( agentContext.wallet.handle, - unqualifiedDid, + namespaceIdentifier, indySdkSchemaFromAnonCreds(legacySchemaId, schema, metadata.indyLedgerSchemaSeqNo), tag, 'CL', diff --git a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts index ca1751c4e2..74488d4108 100644 --- a/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts +++ b/packages/indy-sdk/src/anoncreds/utils/__tests__/identifiers.test.ts @@ -114,7 +114,7 @@ describe('identifiers', () => { test('parses legacy schema id', () => { expect(parseSchemaId('SDqTzbVuCowusqGBNbNDjH:2:schema-name:1.0')).toEqual({ did: 'SDqTzbVuCowusqGBNbNDjH', - didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', schemaName: 'schema-name', schemaVersion: '1.0', }) @@ -123,7 +123,7 @@ describe('identifiers', () => { test('parses did:indy schema id', () => { expect(parseSchemaId('did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH/anoncreds/v0/SCHEMA/schema-name/1.0')).toEqual( { - didIdentifier: 'SDqTzbVuCowusqGBNbNDjH', + namespaceIdentifier: 'SDqTzbVuCowusqGBNbNDjH', did: 'did:indy:bcovrin:test:SDqTzbVuCowusqGBNbNDjH', schemaName: 'schema-name', schemaVersion: '1.0', @@ -137,7 +137,7 @@ describe('identifiers', () => { test('parses legacy credential definition id', () => { expect(parseCredentialDefinitionId('TL1EaPFCZ8Si5aUrqScBDt:3:CL:10:TAG')).toEqual({ did: 'TL1EaPFCZ8Si5aUrqScBDt', - didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', schemaSeqNo: '10', tag: 'TAG', }) @@ -147,7 +147,7 @@ describe('identifiers', () => { expect( parseCredentialDefinitionId('did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/10/TAG') ).toEqual({ - didIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', + namespaceIdentifier: 'TL1EaPFCZ8Si5aUrqScBDt', did: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', namespace: 'pool:localtest', schemaSeqNo: '10', @@ -162,7 +162,7 @@ describe('identifiers', () => { parseRevocationRegistryId('5nDyJVP1NrcPAttP3xwMB9:4:5nDyJVP1NrcPAttP3xwMB9:3:CL:56495:npdb:CL_ACCUM:TAG1') ).toEqual({ did: '5nDyJVP1NrcPAttP3xwMB9', - didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', schemaSeqNo: '56495', credentialDefinitionTag: 'npdb', revocationRegistryTag: 'TAG1', @@ -174,7 +174,7 @@ describe('identifiers', () => { parseRevocationRegistryId('did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9/anoncreds/v0/REV_REG_DEF/56495/npdb/TAG1') ).toEqual({ namespace: 'sovrin', - didIdentifier: '5nDyJVP1NrcPAttP3xwMB9', + namespaceIdentifier: '5nDyJVP1NrcPAttP3xwMB9', did: 'did:indy:sovrin:5nDyJVP1NrcPAttP3xwMB9', schemaSeqNo: '56495', credentialDefinitionTag: 'npdb', diff --git a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts index 8300a7ea29..19f1df864c 100644 --- a/packages/indy-sdk/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-sdk/src/anoncreds/utils/identifiers.ts @@ -1,34 +1,39 @@ +/** + * NOTE: this file is availalbe in both the indy-sdk and indy-vdr packages. If making changes to + * this file, make sure to update both files if applicable. + */ + import { DID_INDY_REGEX } from '../../utils/did' const didIndyAnonCredsBase = - /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ + /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ -// did:indy::/anoncreds/v0/SCHEMA// +// did:indy::/anoncreds/v0/SCHEMA// const didIndySchemaIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/SCHEMA/(?.+)/(?[0-9.]+)$` ) -// :2:: +// :2:: const legacyIndySchemaIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ -// did:indy::/anoncreds/v0/CLAIM_DEF// +// did:indy::/anoncreds/v0/CLAIM_DEF// const didIndyCredentialDefinitionIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/CLAIM_DEF/(?[1-9][0-9]*)/(?.+)$` ) -// :3:CL:: +// :3:CL:: const legacyIndyCredentialDefinitionIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ -// did:indy::/anoncreds/v0/REV_REG_DEF/// +// did:indy::/anoncreds/v0/REV_REG_DEF/// const didIndyRevocationRegistryIdRegex = new RegExp( `^${didIndyAnonCredsBase.source}/REV_REG_DEF/(?[1-9][0-9]*)/(?.+)/(?.+)$` ) -// :4::3:CL::CL_ACCUM: +// :4::3:CL::CL_ACCUM: const legacyIndyRevocationRegistryIdRegex = - /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ + /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ // combines both legacy and did:indy anoncreds identifiers and also the issuer id const indySdkAnonCredsRegexes = [ @@ -99,7 +104,7 @@ export function getDidIndyRevocationRegistryId( interface ParsedSchemaId { did: string - didIdentifier: string + namespaceIdentifier: string schemaName: string schemaVersion: string namespace?: string @@ -115,7 +120,7 @@ export function parseSchemaId(schemaId: string) { interface ParsedCredentialDefinitionId { did: string - didIdentifier: string + namespaceIdentifier: string schemaSeqNo: string tag: string namespace?: string @@ -133,7 +138,7 @@ export function parseCredentialDefinitionId(credentialDefinitionId: string) { interface ParsedRevocationRegistryId { did: string - didIdentifier: string + namespaceIdentifier: string schemaSeqNo: string credentialDefinitionTag: string revocationRegistryTag: string diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts index 77689dcde6..a7aba8eab1 100644 --- a/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts @@ -33,7 +33,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { const { alias, role, submitterDid, endpoints } = options.options let did = options.did - let didIdentifier: string + let namespaceIdentifier: string let verificationKey: Key const privateKey = options.secret?.privateKey @@ -52,7 +52,8 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { assertIndySdkWallet(agentContext.wallet) // Parse submitterDid and extract namespace based on the submitter did - const { namespace: submitterNamespace, id: submitterDidIdentifier } = parseIndyDid(submitterDid) + const { namespace: submitterNamespace, namespaceIdentifier: submitterNamespaceIdentifier } = + parseIndyDid(submitterDid) const submitterSigningKey = await verificationKeyForIndyDid(agentContext, submitterDid) // Only supports version 1 did identifier (which is same as did:sov) @@ -68,11 +69,12 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } } - const { namespace, id } = parseIndyDid(did) - didIdentifier = id + const { namespace, namespaceIdentifier: _namespaceIdentifier } = parseIndyDid(did) + namespaceIdentifier = _namespaceIdentifier + verificationKey = Key.fromPublicKeyBase58(options.options.verkey, KeyType.Ed25519) - if (!isLegacySelfCertifiedDid(didIdentifier, options.options.verkey)) { + if (!isLegacySelfCertifiedDid(namespaceIdentifier, options.options.verkey)) { return { didDocumentMetadata: {}, didRegistrationMetadata: {}, @@ -96,17 +98,17 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } else { // Create a new key and calculate did according to the rules for indy did method verificationKey = await agentContext.wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) - didIdentifier = legacyIndyDidFromPublicKeyBase58(verificationKey.publicKeyBase58) - did = `did:indy:${submitterNamespace}:${didIdentifier}` + namespaceIdentifier = legacyIndyDidFromPublicKeyBase58(verificationKey.publicKeyBase58) + did = `did:indy:${submitterNamespace}:${namespaceIdentifier}` } const pool = indySdkPoolService.getPoolForNamespace(submitterNamespace) await this.registerPublicDid( agentContext, pool, - submitterDidIdentifier, + submitterNamespaceIdentifier, submitterSigningKey, - didIdentifier, + namespaceIdentifier, verificationKey, alias, role @@ -119,7 +121,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { if (endpoints) { const keyAgreementId = `${did}#key-agreement-1` - await this.setEndpointsForDid(agentContext, pool, didIdentifier, verificationKey, endpoints) + await this.setEndpointsForDid(agentContext, pool, namespaceIdentifier, verificationKey, endpoints) didDocumentBuilder .addContext('https://w3id.org/security/suites/x25519-2019/v1') @@ -199,7 +201,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } } - public async registerPublicDid( + private async registerPublicDid( agentContext: AgentContext, pool: IndySdkPool, unqualifiedSubmitterDid: string, @@ -249,7 +251,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } } - public async setEndpointsForDid( + private async setEndpointsForDid( agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string, @@ -296,9 +298,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar { } } -export interface IndySdkIndyDidCreateOptions extends DidCreateOptions { - method: 'indy' - did?: string +interface IndySdkIndyDidCreateOptionsBase extends DidCreateOptions { // The indy sdk can only publish a very limited did document (what is mostly known as a legacy did:sov did) and thus we require everything // needed to construct the did document to be passed through the options object. didDocument?: never @@ -313,3 +313,15 @@ export interface IndySdkIndyDidCreateOptions extends DidCreateOptions { privateKey?: Buffer } } + +interface IndySdkIndyDidCreateOptionsWithDid extends IndySdkIndyDidCreateOptionsBase { + method?: never + did: string +} + +interface IndySdkIndyDidCreateOptionsWithoutDid extends IndySdkIndyDidCreateOptionsBase { + method: 'indy' + did?: never +} + +export type IndySdkIndyDidCreateOptions = IndySdkIndyDidCreateOptionsWithDid | IndySdkIndyDidCreateOptionsWithoutDid diff --git a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts index 836ed8040b..4aa0ddf1d3 100644 --- a/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkIndyDidResolver.ts @@ -18,13 +18,13 @@ export class IndySdkIndyDidResolver implements DidResolver { const didDocumentMetadata = {} try { - const { id: unqualifiedDid, namespace } = parseIndyDid(did) + const { namespaceIdentifier, namespace } = parseIndyDid(did) const poolService = agentContext.dependencyManager.resolve(IndySdkPoolService) const pool = poolService.getPoolForNamespace(namespace) - const nym = await this.getPublicDid(agentContext, pool, unqualifiedDid) - const endpoints = await this.getEndpointsForDid(agentContext, pool, unqualifiedDid) + const nym = await this.getPublicDid(agentContext, pool, namespaceIdentifier) + const endpoints = await this.getEndpointsForDid(agentContext, pool, namespaceIdentifier) // For modern did:indy DIDs, we assume that GET_NYM is always a full verkey in base58. // For backwards compatibility, we accept a shortened verkey and convert it using previous convention diff --git a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts index bbbeec71f8..ff6afc9571 100644 --- a/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts +++ b/packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts @@ -23,7 +23,10 @@ export class IndySdkSovDidResolver implements DidResolver { const keyAgreementId = `${parsed.did}#key-agreement-1` const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) - addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) + + if (endpoints) { + addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) + } return { didDocument: builder.build(), @@ -52,35 +55,39 @@ export class IndySdkSovDidResolver implements DidResolver { return await indySdk.parseGetNymResponse(response) } - private async getEndpointsForDid(agentContext: AgentContext, pool: IndySdkPool, did: string) { + private async getEndpointsForDid(agentContext: AgentContext, pool: IndySdkPool, unqualifiedDid: string) { const indySdk = agentContext.dependencyManager.resolve(IndySdkSymbol) const indySdkPoolService = agentContext.dependencyManager.resolve(IndySdkPoolService) try { - agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.didIndyNamespace}'`) + agentContext.config.logger.debug( + `Get endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'` + ) - const request = await indySdk.buildGetAttribRequest(null, did, 'endpoint', null, null) + const request = await indySdk.buildGetAttribRequest(null, unqualifiedDid, 'endpoint', null, null) agentContext.config.logger.debug( - `Submitting get endpoint ATTRIB request for did '${did}' to ledger '${pool.didIndyNamespace}'` + `Submitting get endpoint ATTRIB request for did '${unqualifiedDid}' to ledger '${pool.didIndyNamespace}'` ) const response = await indySdkPoolService.submitReadRequest(pool, request) - if (!response.result.data) return {} + if (!response.result.data) return null const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib agentContext.config.logger.debug( - `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.didIndyNamespace}'`, + `Got endpoints '${JSON.stringify(endpoints)}' for did '${unqualifiedDid}' from ledger '${ + pool.didIndyNamespace + }'`, { response, endpoints, } ) - return endpoints ?? {} + return endpoints ?? null } catch (error) { agentContext.config.logger.error( - `Error retrieving endpoints for did '${did}' from ledger '${pool.didIndyNamespace}'`, + `Error retrieving endpoints for did '${unqualifiedDid}' from ledger '${pool.didIndyNamespace}'`, { error, } diff --git a/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts index 4d4390bd24..b087c499f5 100644 --- a/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts +++ b/packages/indy-sdk/src/dids/__tests__/IndySdkIndyDidRegistrar.test.ts @@ -78,7 +78,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if both did and privateKey are provided', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'did:indy:pool1:did-value', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -120,7 +119,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if did is provided, but it is not a valid did:indy did', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'BzCbsNYhMrjHiqZDTUASHg', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -141,7 +139,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if did is provided, but no verkey', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'BzCbsNYhMrjHiqZDTUASHg', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -161,7 +158,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if did and verkey are provided, but the did is not self certifying', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -182,7 +178,6 @@ describe('IndySdkIndyDidRegistrar', () => { test('returns an error state if did is provided, but does not match with the namespace from the submitterDid', async () => { const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'did:indy:pool2:R1xKJw17sUoXhejEpugMYJ', options: { submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', @@ -205,7 +200,9 @@ describe('IndySdkIndyDidRegistrar', () => { test('creates a did:indy document without services', async () => { const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) const result = await indySdkIndyDidRegistrar.create(agentContext, { @@ -264,11 +261,12 @@ describe('IndySdkIndyDidRegistrar', () => { }) test('creates a did:indy document by passing did', async () => { - const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) const result = await indySdkIndyDidRegistrar.create(agentContext, { - method: 'indy', did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ', options: { verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', @@ -323,10 +321,14 @@ describe('IndySdkIndyDidRegistrar', () => { test('creates a did:indy document with services', async () => { const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) - const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) const result = await indySdkIndyDidRegistrar.create(agentContext, { @@ -427,10 +429,14 @@ describe('IndySdkIndyDidRegistrar', () => { test('stores the did document', async () => { const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') - const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const registerPublicDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'registerPublicDid') registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) - const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore method is private + const setEndpointsForDidSpy = jest.spyOn(indySdkIndyDidRegistrar, 'setEndpointsForDid') setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) const saveCalled = jest.fn() diff --git a/packages/indy-sdk/src/dids/didIndyUtil.ts b/packages/indy-sdk/src/dids/didIndyUtil.ts index af8adacf77..928ae1007e 100644 --- a/packages/indy-sdk/src/dids/didIndyUtil.ts +++ b/packages/indy-sdk/src/dids/didIndyUtil.ts @@ -14,8 +14,8 @@ import { DID_INDY_REGEX } from '../utils/did' export function parseIndyDid(did: string) { const match = did.match(DID_INDY_REGEX) if (match) { - const [, namespace, id] = match - return { namespace, id } + const [, namespace, namespaceIdentifier] = match + return { namespace, namespaceIdentifier } } else { throw new AriesFrameworkError(`${did} is not a valid did:indy did`) } diff --git a/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts b/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts index c007eaa561..04781e2e62 100644 --- a/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts @@ -9,12 +9,10 @@ import { legacyIndyDidFromPublicKeyBase58 } from '../src/utils/did' import { getIndySdkModules } from './setupIndySdkModule' const agentOptions = getAgentOptions('Indy Sdk Indy Did Registrar', {}, getIndySdkModules()) +const agent = new Agent(agentOptions) -describe('dids', () => { - let agent: Agent> - +describe('Indy SDK Indy Did Registrar', () => { beforeAll(async () => { - agent = new Agent(agentOptions) await agent.initialize() }) diff --git a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts index 77e034941d..12a7cd6848 100644 --- a/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts +++ b/packages/indy-sdk/tests/indy-sdk-anoncreds-registry.e2e.test.ts @@ -8,7 +8,6 @@ import { } from '../../core/tests/helpers' import { IndySdkAnonCredsRegistry } from '../src/anoncreds/services/IndySdkAnonCredsRegistry' import { IndySdkPoolService } from '../src/ledger' -import { assertIndySdkWallet } from '../src/utils/assertIndySdkWallet' import { credentialDefinitionValue } from './__fixtures__/anoncreds' import { getIndySdkModules, indySdk } from './setupIndySdkModule' @@ -136,7 +135,7 @@ describe('IndySdkAnonCredsRegistry', () => { tag: 'TAG', schemaId: didIndySchemaId, type: 'CL', - value: {}, + value: credentialDefinitionValue, }, credentialDefinitionId: didIndyCredentialDefinitionId, state: 'finished', @@ -188,8 +187,6 @@ describe('IndySdkAnonCredsRegistry', () => { resolutionMetadata: {}, }) - assertIndySdkWallet(agent.context.wallet) - // We don't support creating a revocation registry using AFJ yet, so we directly use indy-sdk to register the revocation registry const legacyRevocationRegistryId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` const didIndyRevocationRegistryId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` @@ -214,6 +211,27 @@ describe('IndySdkAnonCredsRegistry', () => { await indySdkPoolService.submitWriteRequest(agent.context, pool, revocationRegistryRequest, signingKey) + // indySdk.buildRevRegEntry panics, so we just pass a custom request directly + const entryResponse = await indySdkPoolService.submitWriteRequest( + agent.context, + pool, + { + identifier: legacyIssuerId, + operation: { + revocDefType: 'CL_ACCUM', + revocRegDefId: legacyRevocationRegistryId, + type: '114', + value: { + accum: + '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', + }, + }, + protocolVersion: 2, + reqId: Math.floor(Math.random() * 1000000), + }, + signingKey + ) + const legacyRevocationRegistryDefinition = await indySdkAnonCredsRegistry.getRevocationRegistryDefinition( agent.context, legacyRevocationRegistryId @@ -274,27 +292,6 @@ describe('IndySdkAnonCredsRegistry', () => { resolutionMetadata: {}, }) - // indySdk.buildRevRegEntry panics, so we just pass a custom request directly - const entryResponse = await indySdkPoolService.submitWriteRequest( - agent.context, - pool, - { - identifier: legacyIssuerId, - operation: { - revocDefType: 'CL_ACCUM', - revocRegDefId: legacyRevocationRegistryId, - type: '114', - value: { - accum: - '1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000 1 0000000000000000000000000000000000000000000000000000000000000000', - }, - }, - protocolVersion: 2, - reqId: Math.floor(Math.random() * 1000000), - }, - signingKey - ) - const legacyRevocationStatusList = await indySdkAnonCredsRegistry.getRevocationStatusList( agent.context, legacyRevocationRegistryId, diff --git a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts index ef220a59a3..fd33a35696 100644 --- a/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts +++ b/packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts @@ -19,7 +19,7 @@ describe('Indy SDK Sov DID resolver', () => { await agent.wallet.delete() }) - it('should resolve a did:sov did', async () => { + test('resolve a did:sov did', async () => { // Add existing endorser did to the wallet const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( agent, @@ -43,8 +43,8 @@ describe('Indy SDK Sov DID resolver', () => { if (!createResult.didState.did) throw new AriesFrameworkError('Unable to register did') - const { id: unqualifiedDid } = parseIndyDid(createResult.didState.did) - const sovDid = `did:sov:${unqualifiedDid}` + const { namespaceIdentifier } = parseIndyDid(createResult.didState.did) + const sovDid = `did:sov:${namespaceIdentifier}` const didResult = await agent.dids.resolve(sovDid) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ diff --git a/packages/indy-vdr/package.json b/packages/indy-vdr/package.json index 088c8da018..98b6d946f5 100644 --- a/packages/indy-vdr/package.json +++ b/packages/indy-vdr/package.json @@ -26,10 +26,10 @@ "dependencies": { "@aries-framework/anoncreds": "0.3.3", "@aries-framework/core": "0.3.3", - "@hyperledger/indy-vdr-shared": "^0.1.0-dev.6" + "@hyperledger/indy-vdr-shared": "^0.1.0-dev.10" }, "devDependencies": { - "@hyperledger/indy-vdr-nodejs": "^0.1.0-dev.6", + "@hyperledger/indy-vdr-nodejs": "^0.1.0-dev.10", "@stablelib/ed25519": "^1.0.2", "rimraf": "^4.0.7", "rxjs": "^7.2.0", diff --git a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts index 4b787414b6..ca2c1149f0 100644 --- a/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts +++ b/packages/indy-vdr/src/anoncreds/IndyVdrAnonCredsRegistry.ts @@ -8,10 +8,10 @@ import type { RegisterCredentialDefinitionReturn, GetRevocationStatusListReturn, GetRevocationRegistryDefinitionReturn, + AnonCredsRevocationRegistryDefinition, } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' -import { getKeyFromVerificationMethod, DidsApi } from '@aries-framework/core' import { GetSchemaRequest, SchemaRequest, @@ -22,15 +22,19 @@ import { GetRevocationRegistryDefinitionRequest, } from '@hyperledger/indy-vdr-shared' +import { parseIndyDid, verificationKeyForIndyDid } from '../dids/didIndyUtil' import { IndyVdrPoolService } from '../pool' import { - didFromSchemaId, - didFromCredentialDefinitionId, - didFromRevocationRegistryDefinitionId, getLegacySchemaId, getLegacyCredentialDefinitionId, indyVdrAnonCredsRegistryIdentifierRegex, + parseSchemaId, + getDidIndySchemaId, + parseCredentialDefinitionId, + getDidIndyCredentialDefinitionId, + parseRevocationRegistryId, + getLegacyRevocationRegistryId, } from './utils/identifiers' import { anonCredsRevocationStatusListFromIndyVdr } from './utils/transform' @@ -41,12 +45,14 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { try { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const did = didFromSchemaId(schemaId) - - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) - + // parse schema id (supports did:indy and legacy) + const { did, namespaceIdentifier, schemaName, schemaVersion } = parseSchemaId(schemaId) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting schema '${schemaId}' from ledger '${pool.indyNamespace}'`) - const request = new GetSchemaRequest({ submitterDid: did, schemaId }) + + // even though we support did:indy and legacy identifiers we always need to fetch using the legacy identifier + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, schemaName, schemaVersion) + const request = new GetSchemaRequest({ schemaId: legacySchemaId }) agentContext.config.logger.trace( `Submitting get schema request for schema '${schemaId}' to ledger '${pool.indyNamespace}'` @@ -57,36 +63,34 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { response, }) - const issuerId = didFromSchemaId(schemaId) + if (!('attr_names' in response.result.data)) { + agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`) - if ('attr_names' in response.result.data) { return { - schema: { - attrNames: response.result.data.attr_names, - name: response.result.data.name, - version: response.result.data.version, - issuerId, - }, - schemaId: schemaId, - resolutionMetadata: {}, - schemaMetadata: { - didIndyNamespace: pool.indyNamespace, - // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. - // For this reason we return it in the metadata. - indyLedgerSeqNo: response.result.seqNo, + schemaId, + resolutionMetadata: { + error: 'notFound', + message: `unable to find schema with id ${schemaId}`, }, + schemaMetadata: {}, } } - agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`) - return { + schema: { + attrNames: response.result.data.attr_names, + name: response.result.data.name, + version: response.result.data.version, + issuerId: did, + }, schemaId, - resolutionMetadata: { - error: 'notFound', - message: `unable to find schema with id ${schemaId}`, + resolutionMetadata: {}, + schemaMetadata: { + didIndyNamespace: pool.indyNamespace, + // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. + // For this reason we return it in the metadata. + indyLedgerSeqNo: response.result.seqNo, }, - schemaMetadata: {}, } } catch (error) { agentContext.config.logger.error(`Error retrieving schema '${schemaId}'`, { @@ -106,27 +110,33 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { public async registerSchema( agentContext: AgentContext, - options: IndyVdrRegisterSchemaOptions + options: RegisterSchemaOptions ): Promise { - if (!options.options.didIndyNamespace) { - return { - schemaMetadata: {}, - registrationMetadata: {}, - schemaState: { - reason: 'no didIndyNamespace defined in the options. didIndyNamespace is required when using the Indy VDR', - schema: options.schema, - state: 'failed', - }, - } - } - try { + // This will throw an error if trying to register a schema with a legacy indy identifier. We only support did:indy identifiers + // for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { namespaceIdentifier, namespace } = parseIndyDid(options.schema.issuerId) + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + const pool = indyVdrPoolService.getPoolForNamespace(namespace) + agentContext.config.logger.debug( + `Register schema on ledger '${pool.indyNamespace}' with did '${options.schema.issuerId}'`, + options.schema + ) + + const didIndySchemaId = getDidIndySchemaId( + namespace, + namespaceIdentifier, + options.schema.name, + options.schema.version + ) + const legacySchemaId = getLegacySchemaId(namespaceIdentifier, options.schema.name, options.schema.version) + const schemaRequest = new SchemaRequest({ - submitterDid: options.schema.issuerId, + submitterDid: namespaceIdentifier, schema: { - id: getLegacySchemaId(options.schema.issuerId, options.schema.name, options.schema.version), + id: legacySchemaId, name: options.schema.name, ver: '1.0', version: options.schema.version, @@ -134,29 +144,12 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { }, }) - const pool = indyVdrPoolService.getPoolForNamespace(options.options.didIndyNamespace) - - // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did - // from the ledger to know which key is associated with the did - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didResult = await didsApi.resolve(`did:sov:${options.schema.issuerId}`) - - if (!didResult.didDocument) { - return { - schemaMetadata: {}, - registrationMetadata: {}, - schemaState: { - schema: options.schema, - state: 'failed', - reason: `didNotFound: unable to resolve did did:sov:${options.schema.issuerId}: ${didResult.didResolutionMetadata.message}`, - }, - } - } - - const verificationMethod = didResult.didDocument.dereferenceKey(`did:sov:${options.schema.issuerId}#key-1`) - const key = getKeyFromVerificationMethod(verificationMethod) - - const response = await pool.submitWriteRequest(agentContext, schemaRequest, key) + const submitterKey = await verificationKeyForIndyDid(agentContext, options.schema.issuerId) + const response = await pool.submitWriteRequest(agentContext, schemaRequest, submitterKey) + agentContext.config.logger.debug(`Registered schema '${didIndySchemaId}' on ledger '${pool.indyNamespace}'`, { + response, + schemaRequest, + }) return { schemaState: { @@ -167,14 +160,13 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { name: options.schema.name, version: options.schema.version, }, - schemaId: getLegacySchemaId(options.schema.issuerId, options.schema.name, options.schema.version), + schemaId: didIndySchemaId, }, registrationMetadata: {}, schemaMetadata: { // NOTE: the seqNo is required by the indy-sdk even though not present in AnonCreds v1. // For this reason we return it in the metadata. indyLedgerSeqNo: response.result.txnMetadata.seqNo, - didIndyNamespace: pool.indyNamespace, }, } } catch (error) { @@ -203,53 +195,58 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { try { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const did = didFromCredentialDefinitionId(credentialDefinitionId) - - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) + // we support did:indy and legacy identifiers + const { did, namespaceIdentifier, schemaSeqNo, tag } = parseCredentialDefinitionId(credentialDefinitionId) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Getting credential definition '${credentialDefinitionId}' from ledger '${pool.indyNamespace}'` ) + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, tag) const request = new GetCredentialDefinitionRequest({ - submitterDid: did, - credentialDefinitionId, + credentialDefinitionId: legacyCredentialDefinitionId, }) agentContext.config.logger.trace( `Submitting get credential definition request for credential definition '${credentialDefinitionId}' to ledger '${pool.indyNamespace}'` ) - const response = await pool.submitReadRequest(request) - const schema = await this.fetchIndySchemaWithSeqNo(agentContext, response.result.ref, did) + // We need to fetch the schema to determine the schemaId (we only have the seqNo) + const schema = await this.fetchIndySchemaWithSeqNo(agentContext, response.result.ref, namespaceIdentifier) + + if (!schema || !response.result.data) { + agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`) - if (response.result.data && schema) { return { - credentialDefinitionId: credentialDefinitionId, - credentialDefinition: { - issuerId: didFromCredentialDefinitionId(credentialDefinitionId), - schemaId: schema.schema.schemaId, - tag: response.result.tag, - type: 'CL', - value: response.result.data, - }, - credentialDefinitionMetadata: { - didIndyNamespace: pool.indyNamespace, + credentialDefinitionId, + credentialDefinitionMetadata: {}, + resolutionMetadata: { + error: 'notFound', + message: `unable to resolve credential definition with id ${credentialDefinitionId}`, }, - resolutionMetadata: {}, } } - agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`) + // Format the schema id based on the type of the credential definition id + const schemaId = credentialDefinitionId.startsWith('did:indy') + ? getDidIndySchemaId(pool.indyNamespace, namespaceIdentifier, schema.schema.name, schema.schema.version) + : schema.schema.schemaId return { - credentialDefinitionId, - credentialDefinitionMetadata: {}, - resolutionMetadata: { - error: 'notFound', - message: `unable to resolve credential definition with id ${credentialDefinitionId}`, + credentialDefinitionId: credentialDefinitionId, + credentialDefinition: { + issuerId: did, + schemaId, + tag: response.result.tag, + type: 'CL', + value: response.result.data, + }, + credentialDefinitionMetadata: { + didIndyNamespace: pool.indyNamespace, }, + resolutionMetadata: {}, } } catch (error) { agentContext.config.logger.error(`Error retrieving credential definition '${credentialDefinitionId}'`, { @@ -270,26 +267,22 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { public async registerCredentialDefinition( agentContext: AgentContext, - options: IndyVdrRegisterCredentialDefinitionOptions + options: RegisterCredentialDefinitionOptions ): Promise { - // Make sure didIndyNamespace is passed - if (!options.options.didIndyNamespace) { - return { - credentialDefinitionMetadata: {}, - registrationMetadata: {}, - credentialDefinitionState: { - reason: 'no didIndyNamespace defined in the options. didIndyNamespace is required when using the Indy SDK', - credentialDefinition: options.credentialDefinition, - state: 'failed', - }, - } - } - try { + // This will throw an error if trying to register a credential defintion with a legacy indy identifier. We only support did:indy + // identifiers for registering, that will allow us to extract the namespace and means all stored records will use did:indy identifiers. + const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId) + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const pool = indyVdrPoolService.getPoolForNamespace(options.options.didIndyNamespace) + const pool = indyVdrPoolService.getPoolForNamespace(namespace) + agentContext.config.logger.debug( + `Registering credential definition on ledger '${pool.indyNamespace}' with did '${options.credentialDefinition.issuerId}'`, + options.credentialDefinition + ) + // TODO: this will bypass caching if done on a higher level. const { schema, schemaMetadata, resolutionMetadata } = await this.getSchema( agentContext, options.credentialDefinition.schemaId @@ -309,17 +302,23 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } } - const credentialDefinitionId = getLegacyCredentialDefinitionId( + const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId( options.credentialDefinition.issuerId, schemaMetadata.indyLedgerSeqNo, options.credentialDefinition.tag ) + const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId( + namespace, + namespaceIdentifier, + schemaMetadata.indyLedgerSeqNo, + options.credentialDefinition.tag + ) const credentialDefinitionRequest = new CredentialDefinitionRequest({ - submitterDid: options.credentialDefinition.issuerId, + submitterDid: namespaceIdentifier, credentialDefinition: { ver: '1.0', - id: credentialDefinitionId, + id: legacyCredentialDefinitionId, schemaId: `${schemaMetadata.indyLedgerSeqNo}`, type: 'CL', tag: options.credentialDefinition.tag, @@ -327,32 +326,10 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { }, }) - // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did - // from the ledger to know which key is associated with the did - const didsApi = agentContext.dependencyManager.resolve(DidsApi) - const didResult = await didsApi.resolve(`did:sov:${options.credentialDefinition.issuerId}`) - - if (!didResult.didDocument) { - return { - credentialDefinitionMetadata: {}, - registrationMetadata: {}, - credentialDefinitionState: { - credentialDefinition: options.credentialDefinition, - state: 'failed', - reason: `didNotFound: unable to resolve did did:sov:${options.credentialDefinition.issuerId}: ${didResult.didResolutionMetadata.message}`, - }, - } - } - - const verificationMethod = didResult.didDocument.dereferenceKey( - `did:sov:${options.credentialDefinition.issuerId}#key-1` - ) - const key = getKeyFromVerificationMethod(verificationMethod) - - const response = await pool.submitWriteRequest(agentContext, credentialDefinitionRequest, key) - + const submitterKey = await verificationKeyForIndyDid(agentContext, options.credentialDefinition.issuerId) + const response = await pool.submitWriteRequest(agentContext, credentialDefinitionRequest, submitterKey) agentContext.config.logger.debug( - `Registered credential definition '${credentialDefinitionId}' on ledger '${pool.indyNamespace}'`, + `Registered credential definition '${didIndyCredentialDefinitionId}' on ledger '${pool.indyNamespace}'`, { response, credentialDefinition: options.credentialDefinition, @@ -360,12 +337,10 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ) return { - credentialDefinitionMetadata: { - didIndyNamespace: pool.indyNamespace, - }, + credentialDefinitionMetadata: {}, credentialDefinitionState: { credentialDefinition: options.credentialDefinition, - credentialDefinitionId, + credentialDefinitionId: didIndyCredentialDefinitionId, state: 'finished', }, registrationMetadata: {}, @@ -398,23 +373,28 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ): Promise { try { const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const did = didFromRevocationRegistryDefinitionId(revocationRegistryDefinitionId) - const pool = await indySdkPoolService.getPoolForDid(agentContext, did) + const { did, namespaceIdentifier, credentialDefinitionTag, revocationRegistryTag, schemaSeqNo } = + parseRevocationRegistryId(revocationRegistryDefinitionId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.indyNamespace}' to retrieve revocation registry definition '${revocationRegistryDefinitionId}'` ) + const legacyRevocationRegistryId = getLegacyRevocationRegistryId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) const request = new GetRevocationRegistryDefinitionRequest({ - submitterDid: did, - revocationRegistryId: revocationRegistryDefinitionId, + revocationRegistryId: legacyRevocationRegistryId, }) agentContext.config.logger.trace( `Submitting get revocation registry definition request for revocation registry definition '${revocationRegistryDefinitionId}' to ledger` ) - const response = await pool.submitReadRequest(request) if (!response.result.data) { @@ -435,28 +415,44 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } } + agentContext.config.logger.trace( + `Got revocation registry definition '${revocationRegistryDefinitionId}' from ledger '${pool.indyNamespace}'`, + { + response, + } + ) + + const credentialDefinitionId = revocationRegistryDefinitionId.startsWith('did:indy:') + ? getDidIndyCredentialDefinitionId( + pool.indyNamespace, + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag + ) + : getLegacyCredentialDefinitionId(namespaceIdentifier, schemaSeqNo, credentialDefinitionTag) + const revocationRegistryDefinition = { issuerId: did, - revocDefType: response.result.data?.revocDefType, + revocDefType: response.result.data.revocDefType, value: { - maxCredNum: response.result.data?.value.maxCredNum, - tailsHash: response.result.data?.value.tailsHash, - tailsLocation: response.result.data?.value.tailsLocation, + maxCredNum: response.result.data.value.maxCredNum, + tailsHash: response.result.data.value.tailsHash, + tailsLocation: response.result.data.value.tailsLocation, publicKeys: { accumKey: { - z: response.result.data?.value.publicKeys.accumKey.z, + z: response.result.data.value.publicKeys.accumKey.z, }, }, }, - tag: response.result.data?.tag, - credDefId: response.result.data?.credDefId, - } + tag: response.result.data.tag, + credDefId: credentialDefinitionId, + } satisfies AnonCredsRevocationRegistryDefinition return { revocationRegistryDefinitionId, revocationRegistryDefinition, revocationRegistryDefinitionMetadata: { - issuanceType: response.result.data?.value.issuanceType, + issuanceType: response.result.data.value.issuanceType, didIndyNamespace: pool.indyNamespace, }, resolutionMetadata: {}, @@ -488,24 +484,29 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { ): Promise { try { const indySdkPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const did = didFromRevocationRegistryDefinitionId(revocationRegistryId) - const pool = await indySdkPoolService.getPoolForDid(agentContext, did) + const { did, namespaceIdentifier, schemaSeqNo, credentialDefinitionTag, revocationRegistryTag } = + parseRevocationRegistryId(revocationRegistryId) + const { pool } = await indySdkPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug( `Using ledger '${pool.indyNamespace}' to retrieve revocation registry deltas with revocation registry definition id '${revocationRegistryId}' until ${timestamp}` ) + const legacyRevocationRegistryId = getLegacyRevocationRegistryId( + namespaceIdentifier, + schemaSeqNo, + credentialDefinitionTag, + revocationRegistryTag + ) const request = new GetRevocationRegistryDeltaRequest({ - submitterDid: did, - revocationRegistryId, + revocationRegistryId: legacyRevocationRegistryId, toTs: timestamp, }) agentContext.config.logger.trace( `Submitting get revocation registry delta request for revocation registry '${revocationRegistryId}' to ledger` ) - const response = await pool.submitReadRequest(request) agentContext.config.logger.debug( @@ -543,9 +544,9 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { } const revocationRegistryDelta = { - accum: response.result.data?.value.accum_to.value.accum, - issued: response.result.data?.value.issued, - revoked: response.result.data?.value.revoked, + accum: response.result.data.value.accum_to.value.accum, + issued: response.result.data.value.issued, + revoked: response.result.data.value.revoked, } return { @@ -583,7 +584,7 @@ export class IndyVdrAnonCredsRegistry implements AnonCredsRegistry { private async fetchIndySchemaWithSeqNo(agentContext: AgentContext, seqNo: number, did: string) { const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, did) agentContext.config.logger.debug(`Getting transaction with seqNo '${seqNo}' from ledger '${pool.indyNamespace}'`) // ledgerType 1 is domain ledger @@ -622,15 +623,3 @@ interface SchemaType { name: string } } - -export interface IndyVdrRegisterSchemaOptions extends RegisterSchemaOptions { - options: { - didIndyNamespace: string - } -} - -export interface IndyVdrRegisterCredentialDefinitionOptions extends RegisterCredentialDefinitionOptions { - options: { - didIndyNamespace: string - } -} diff --git a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifier.test.ts b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifier.test.ts deleted file mode 100644 index 62528a0075..0000000000 --- a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifier.test.ts +++ /dev/null @@ -1,52 +0,0 @@ -import { - getLegacySchemaId, - getLegacyCredentialDefinitionId, - didFromSchemaId, - didFromCredentialDefinitionId, - indyVdrAnonCredsRegistryIdentifierRegex, -} from '../identifiers' - -describe('identifiers', () => { - it('matches against a legacy indy did, schema id, credential definition id and revocation registry id', () => { - const did = '7Tqg6BwSSWapxgUDm9KKgg' - const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' - const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' - const revocationRegistryId = - 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' - - const anotherId = 'some:id' - - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) - expect(indyVdrAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) - }) - - it('getLegacySchemaId should return a valid schema Id', () => { - const did = '29347' - const name = 'starlinks' - const version = '321' - - expect(getLegacySchemaId(did, name, version)).toEqual(`29347:2:starlinks:321`) - }) - - it('getLegacyCredentialDefinition should return a valid Credential Id', () => { - const did = '15565' - const seqNo = 323 - const tag = 'indyTag' - expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('15565:3:CL:323:indyTag') - }) - - it('didFromSchemaId should return the valid did from the schema', () => { - const schemaId = '29347:2:starlinks:321' - - expect(didFromSchemaId(schemaId)).toEqual('29347') - }) - - it('didFromCredentialId should return the valid did from the schema', () => { - const credentialDefinitionId = '15565:3:CL:323:indyTag' - - expect(didFromCredentialDefinitionId(credentialDefinitionId)).toEqual('15565') - }) -}) diff --git a/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts new file mode 100644 index 0000000000..555605a1d9 --- /dev/null +++ b/packages/indy-vdr/src/anoncreds/utils/_tests_/identifiers.test.ts @@ -0,0 +1,185 @@ +import { + getDidIndyCredentialDefinitionId, + getDidIndyRevocationRegistryId, + getDidIndySchemaId, + getLegacyCredentialDefinitionId, + getLegacyRevocationRegistryId, + getLegacySchemaId, + indyVdrAnonCredsRegistryIdentifierRegex, + parseCredentialDefinitionId, + parseRevocationRegistryId, + parseSchemaId, +} from '../identifiers' + +describe('identifiers', () => { + describe('indyVdrAnonCredsRegistryIdentifierRegex', () => { + test('matches against a legacy indy did, schema id, credential definition id and revocation registry id', () => { + const did = '7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'BQ42WeE24jFHeyGg8x9XAz:2:Medical Bill:1.0' + const credentialDefinitionId = 'N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID' + const revocationRegistryId = + 'N7baRMcyvPwWc8v85CtZ6e:4:N7baRMcyvPwWc8v85CtZ6e:3:CL:100669:SCH Employee ID:CL_ACCUM:1-1024' + + const anotherId = 'some:id' + + // unqualified issuerId not in regex on purpose. See note in implementation. + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(did)).toEqual(false) + + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + + test('matches against a did indy did, schema id, credential definition id and revocation registry id', () => { + const did = 'did:indy:local:7Tqg6BwSSWapxgUDm9KKgg' + const schemaId = 'did:indy:local:BQ42WeE24jFHeyGg8x9XAz/anoncreds/v0/SCHEMA/Medical Bill/1.0' + const credentialDefinitionId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/CLAIM_DEF/100669/SCH Employee ID' + const revocationRegistryId = + 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/REV_REG_DEF/100669/SCH Employee ID/1-1024' + + const anotherId = 'did:indy:local:N7baRMcyvPwWc8v85CtZ6e/anoncreds/v0/SOME_DEF' + + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(did)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(schemaId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(credentialDefinitionId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(revocationRegistryId)).toEqual(true) + expect(indyVdrAnonCredsRegistryIdentifierRegex.test(anotherId)).toEqual(false) + }) + }) + + test('getLegacySchemaId returns a valid schema id given a did, name, and version', () => { + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getLegacySchemaId(did, name, version)).toEqual('12345:2:backbench:420') + }) + + test('getLegacyCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getLegacyCredentialDefinitionId(did, seqNo, tag)).toEqual('12345:3:CL:420:someTag') + }) + + test('getLegacyRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getLegacyRevocationRegistryId(did, seqNo, credentialDefinitionTag, tag)).toEqual( + '12345:4:12345:3:CL:420:someTag:CL_ACCUM:anotherTag' + ) + }) + + test('getDidIndySchemaId returns a valid schema id given a did, name, and version', () => { + const namespace = 'sovrin:test' + const did = '12345' + const name = 'backbench' + const version = '420' + + expect(getDidIndySchemaId(namespace, did, name, version)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/SCHEMA/backbench/420' + ) + }) + + test('getDidIndyCredentialDefinitionId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const tag = 'someTag' + + expect(getDidIndyCredentialDefinitionId(namespace, did, seqNo, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/CLAIM_DEF/420/someTag' + ) + }) + + test('getDidIndyRevocationRegistryId returns a valid credential definition id given a did, seqNo, and tag', () => { + const namespace = 'sovrin:test' + const did = '12345' + const seqNo = 420 + const credentialDefinitionTag = 'someTag' + const tag = 'anotherTag' + + expect(getDidIndyRevocationRegistryId(namespace, did, seqNo, credentialDefinitionTag, tag)).toEqual( + 'did:indy:sovrin:test:12345/anoncreds/v0/REV_REG_DEF/420/someTag/anotherTag' + ) + }) + + describe('parseSchemaId', () => { + test('parses legacy schema id', () => { + expect(parseSchemaId('SDqTzbVuCowusqGBNbNDjH:2:schema-name:1.0')).toEqual({ + did: 'SDqTzbVuCowusqGBNbNDjH', + 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 d242ca3461..e7e1a2bd49 100644 --- a/packages/indy-vdr/src/anoncreds/utils/identifiers.ts +++ b/packages/indy-vdr/src/anoncreds/utils/identifiers.ts @@ -1,42 +1,156 @@ -export const legacyIndyVdrIssuerIdRegex = /^[a-zA-Z0-9]{21,22}$/ -export const legacyIndyVdrSchemaIdRegex = /^[a-zA-Z0-9]{21,22}:2:.+:[0-9.]+$/ -export const legacyIndyVdrCredentialDefinitionIdRegex = - /^[a-zA-Z0-9]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+)):(.+)?$/ -export const legacyIndyVdrRevocationRegistryIdRegex = - /^[a-zA-Z0-9]{21,22}:4:[a-zA-Z0-9]{21,22}:3:CL:(([1-9][0-9]*)|([a-zA-Z0-9]{21,22}:2:.+:[0-9.]+))(:.+)?:CL_ACCUM:(.+$)/ +/** + * NOTE: this file is availalbe in both the indy-sdk and indy-vdr packages. If making changes to + * this file, make sure to update both files if applicable. + */ + +import { DID_INDY_REGEX } from '../../utils/did' + +const didIndyAnonCredsBase = + /(?did:indy:(?((?:[a-z][_a-z0-9-]*)(?::[a-z][_a-z0-9-]*)?)):(?([1-9A-HJ-NP-Za-km-z]{21,22})))\/anoncreds\/v0/ + +// did:indy::/anoncreds/v0/SCHEMA// +const didIndySchemaIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/SCHEMA/(?.+)/(?[0-9.]+)$` +) + +// :2:: +const legacyIndySchemaIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):2:(?.+):(?[0-9.]+)$/ + +// did:indy::/anoncreds/v0/CLAIM_DEF// +const didIndyCredentialDefinitionIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/CLAIM_DEF/(?[1-9][0-9]*)/(?.+)$` +) + +// :3:CL:: +const legacyIndyCredentialDefinitionIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):3:CL:(?[1-9][0-9]*):(?.+)$/ + +// did:indy::/anoncreds/v0/REV_REG_DEF/// +const didIndyRevocationRegistryIdRegex = new RegExp( + `^${didIndyAnonCredsBase.source}/REV_REG_DEF/(?[1-9][0-9]*)/(?.+)/(?.+)$` +) + +// :4::3:CL::CL_ACCUM: +const legacyIndyRevocationRegistryIdRegex = + /^(?(?[a-zA-Z0-9]{21,22})):4:[a-zA-Z0-9]{21,22}:3:CL:(?[1-9][0-9]*):(?.+):CL_ACCUM:(?.+)$/ + +// combines both legacy and did:indy anoncreds identifiers and also the issuer id +const indyVdrAnonCredsRegexes = [ + // NOTE: we only include the qualified issuer id here, as we don't support registering objects based on legacy issuer ids. + // you can still resolve using legacy issuer ids, but you need to use the full did:indy identifier when registering. + // As we find a matching anoncreds registry based on the issuerId only when creating an object, this will make sure + // it will throw an no registry found for identifier error. + // issuer id + DID_INDY_REGEX, + + // schema + didIndySchemaIdRegex, + legacyIndySchemaIdRegex, + + // credential definition + didIndyCredentialDefinitionIdRegex, + legacyIndyCredentialDefinitionIdRegex, + + // revocation registry + legacyIndyRevocationRegistryIdRegex, + didIndyRevocationRegistryIdRegex, +] export const indyVdrAnonCredsRegistryIdentifierRegex = new RegExp( - `${legacyIndyVdrIssuerIdRegex.source}|${legacyIndyVdrSchemaIdRegex.source}|${legacyIndyVdrCredentialDefinitionIdRegex.source}|${legacyIndyVdrRevocationRegistryIdRegex.source}` + indyVdrAnonCredsRegexes.map((r) => r.source.replace(/(\?<[a-zA-Z]+>)?/g, '')).join('|') ) +export function getDidIndySchemaId(namespace: string, unqualifiedDid: string, name: string, version: string) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/SCHEMA/${name}/${version}` +} + export function getLegacySchemaId(unqualifiedDid: string, name: string, version: string) { return `${unqualifiedDid}:2:${name}:${version}` } -export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: number, tag: string) { +export function getLegacyCredentialDefinitionId(unqualifiedDid: string, seqNo: string | number, tag: string) { return `${unqualifiedDid}:3:CL:${seqNo}:${tag}` } -/** - * Extract did from schema id - */ -export function didFromSchemaId(schemaId: string) { - const [did] = schemaId.split(':') +export function getDidIndyCredentialDefinitionId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + tag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/CLAIM_DEF/${seqNo}/${tag}` +} - return did +// TZQuLp43UcYTdtc3HewcDz:4:TZQuLp43UcYTdtc3HewcDz:3:CL:98158:BaustellenzertifikateNU1:CL_ACCUM:1-100 +export function getLegacyRevocationRegistryId( + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `${unqualifiedDid}:4:${unqualifiedDid}:3:CL:${seqNo}:${credentialDefinitionTag}:CL_ACCUM:${revocationRegistryTag}` } -/** - * Extract did from credential definition id - */ -export function didFromCredentialDefinitionId(credentialDefinitionId: string) { - const [did] = credentialDefinitionId.split(':') +export function getDidIndyRevocationRegistryId( + namespace: string, + unqualifiedDid: string, + seqNo: string | number, + credentialDefinitionTag: string, + revocationRegistryTag: string +) { + return `did:indy:${namespace}:${unqualifiedDid}/anoncreds/v0/REV_REG_DEF/${seqNo}/${credentialDefinitionTag}/${revocationRegistryTag}` +} + +interface ParsedSchemaId { + did: string + namespaceIdentifier: string + schemaName: string + schemaVersion: string + namespace?: string +} + +export function parseSchemaId(schemaId: string) { + const match = schemaId.match(didIndySchemaIdRegex) ?? schemaId.match(legacyIndySchemaIdRegex) + + if (!match) throw new Error(`Invalid schema id: ${schemaId}`) - return did + return match.groups as unknown as ParsedSchemaId } -export function didFromRevocationRegistryDefinitionId(revocationRegistryId: string) { - const [did] = revocationRegistryId.split(':') +interface ParsedCredentialDefinitionId { + did: string + namespaceIdentifier: string + schemaSeqNo: string + tag: string + namespace?: string +} + +export function parseCredentialDefinitionId(credentialDefinitionId: string) { + const match = + credentialDefinitionId.match(didIndyCredentialDefinitionIdRegex) ?? + credentialDefinitionId.match(legacyIndyCredentialDefinitionIdRegex) + + if (!match) throw new Error(`Invalid credential definition id: ${credentialDefinitionId}`) + + return match.groups as unknown as ParsedCredentialDefinitionId +} + +interface ParsedRevocationRegistryId { + did: string + namespaceIdentifier: string + schemaSeqNo: string + credentialDefinitionTag: string + revocationRegistryTag: string + namespace?: string +} + +export function parseRevocationRegistryId(revocationRegistryId: string) { + const match = + revocationRegistryId.match(didIndyRevocationRegistryIdRegex) ?? + revocationRegistryId.match(legacyIndyRevocationRegistryIdRegex) + + if (!match) throw new Error(`Invalid revocation registry id: ${revocationRegistryId}`) - return did + return match.groups as unknown as ParsedRevocationRegistryId } diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts index 50735a67eb..d32e8947ec 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidRegistrar.ts @@ -34,6 +34,7 @@ import { indyDidDocumentFromDid, parseIndyDid, isSelfCertifiedIndyDid, + verificationKeyForIndyDid, } from './didIndyUtil' import { endpointsAttribFromServices } from './didSovUtil' @@ -44,10 +45,10 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { const seed = options.secret?.seed const privateKey = options.secret?.privateKey - const { alias, role, submitterDid, submitterVerkey, services, useEndpointAttrib } = options.options - let verkey = options.options.verkey + const { alias, role, submitterDid, services, useEndpointAttrib } = options.options let did = options.did - let id + let namespaceIdentifier: string + let verificationKey: Key const allowOne = [privateKey, seed, did].filter((e) => e !== undefined) if (allowOne.length > 1) { @@ -62,11 +63,13 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { } try { - const { namespace, id: submitterId } = parseIndyDid(submitterDid) + // Parse submitterDid and extract namespace based on the submitter did + const { namespace: submitterNamespace, namespaceIdentifier: submitterNamespaceIdentifier } = + parseIndyDid(submitterDid) + const submitterSigningKey = await verificationKeyForIndyDid(agentContext, submitterDid) if (did) { - id = parseIndyDid(did).id - if (!verkey) { + if (!options.options.verkey) { return { didDocumentMetadata: {}, didRegistrationMetadata: {}, @@ -76,26 +79,62 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { }, } } - if (!isSelfCertifiedIndyDid(did, verkey)) { - throw new Error(`Initial verkey ${verkey} does not match did ˇ${did}`) + + const { namespace, namespaceIdentifier: _namespaceIdentifier } = parseIndyDid(did) + namespaceIdentifier = _namespaceIdentifier + verificationKey = Key.fromPublicKeyBase58(options.options.verkey, KeyType.Ed25519) + + if (!isSelfCertifiedIndyDid(did, options.options.verkey)) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Initial verkey ${options.options.verkey} does not match did ${did}`, + }, + } + } + + if (submitterNamespace !== namespace) { + return { + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `The submitter did uses namespace ${submitterNamespace} and the did to register uses namespace ${namespace}. Namespaces must match.`, + }, + } } } else { // Create a new key and calculate did according to the rules for indy did method - const key = await agentContext.wallet.createKey({ privateKey, seed, keyType: KeyType.Ed25519 }) - const buffer = Hasher.hash(key.publicKey, 'sha2-256') + verificationKey = await agentContext.wallet.createKey({ privateKey, seed, keyType: KeyType.Ed25519 }) + const buffer = Hasher.hash(verificationKey.publicKey, 'sha2-256') - id = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) - verkey = key.publicKeyBase58 - did = `did:indy:${namespace}:${id}` + namespaceIdentifier = TypedArrayEncoder.toBase58(buffer.slice(0, 16)) + did = `did:indy:${submitterNamespace}:${namespaceIdentifier}` } // Create base did document - const didDocumentBuilder = indyDidDocumentFromDid(did, verkey) + const didDocumentBuilder = indyDidDocumentFromDid(did, verificationKey.publicKeyBase58) let diddocContent // Add services if object was passed if (services) { - services.forEach((item) => didDocumentBuilder.addService(item)) + services.forEach((item) => { + const prependDidIfNotPresent = (id: string) => { + return id.startsWith('#') ? `${did}${id}` : id + } + + // Prepend the did to the service id if it is not already there + item.id = prependDidIfNotPresent(item.id) + + // TODO: should we also prepend the did to routingKeys? + if (item instanceof DidCommV1Service) { + item.recipientKeys = item.recipientKeys.map(prependDidIfNotPresent) + } + + didDocumentBuilder.addService(item) + }) const commTypes = [IndyAgentService.type, DidCommV1Service.type, DidCommV2Service.type] const serviceTypes = new Set(services.map((item) => item.type)) @@ -105,10 +144,11 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { // If there is at least a communication service, add the key agreement key if (commTypes.some((type) => serviceTypes.has(type))) { didDocumentBuilder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') .addVerificationMethod({ controller: did, id: keyAgreementId, - publicKeyBase58: createKeyAgreementKey(verkey), + publicKeyBase58: createKeyAgreementKey(verificationKey.publicKeyBase58), type: 'X25519KeyAgreementKey2019', }) .addKeyAgreement(keyAgreementId) @@ -123,29 +163,36 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { // create diddocContent parameter based on the diff between the base and the resulting DID Document diddocContent = didDocDiff( didDocumentBuilder.build().toJSON(), - indyDidDocumentFromDid(did, verkey).build().toJSON() + indyDidDocumentFromDid(did, verificationKey.publicKeyBase58).build().toJSON() ) } } // Build did document const didDocument = didDocumentBuilder.build() - - const pool = agentContext.dependencyManager.resolve(IndyVdrPoolService).getPoolForNamespace(namespace) - + const pool = agentContext.dependencyManager.resolve(IndyVdrPoolService).getPoolForNamespace(submitterNamespace) // If there are services and we are using legacy indy endpoint attrib, make sure they are suitable before registering the DID if (services && useEndpointAttrib) { const endpoints = endpointsAttribFromServices(services) - await this.registerPublicDid(agentContext, pool, submitterId, submitterVerkey, id, verkey, alias, role) - await this.setEndpointsForDid(agentContext, pool, verkey, id, endpoints) + await this.registerPublicDid( + agentContext, + pool, + submitterNamespaceIdentifier, + submitterSigningKey, + namespaceIdentifier, + verificationKey, + alias, + role + ) + await this.setEndpointsForDid(agentContext, pool, namespaceIdentifier, verificationKey, endpoints) } else { await this.registerPublicDid( agentContext, pool, - submitterId, - submitterVerkey, - id, - verkey, + submitterNamespaceIdentifier, + submitterSigningKey, + namespaceIdentifier, + verificationKey, alias, role, diddocContent @@ -168,7 +215,7 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { return { didDocumentMetadata: {}, didRegistrationMetadata: { - didIndyNamespace: namespace, + didIndyNamespace: submitterNamespace, }, didState: { state: 'finished', @@ -222,41 +269,44 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { private async registerPublicDid( agentContext: AgentContext, pool: IndyVdrPool, - submitterDid: string, - submitterVerkey: string, - targetDid: string, - verkey: string, + unqualifiedSubmitterDid: string, + submitterSigningKey: Key, + unqualifiedDid: string, + signingKey: Key, alias?: string, role?: string, diddocContent?: Record ) { try { - agentContext.config.logger.debug(`Register public did '${targetDid}' on ledger '${pool}'`) + agentContext.config.logger.debug(`Register public did '${unqualifiedDid}' on ledger '${pool}'`) // FIXME: Add diddocContent when supported by indy-vdr if (diddocContent) { throw new IndyVdrError('diddocContent is not yet supported') } - const request = new NymRequest({ submitterDid, dest: targetDid, verkey, alias }) - - const signingKey = Key.fromPublicKeyBase58(submitterVerkey, KeyType.Ed25519) + const request = new NymRequest({ + submitterDid: unqualifiedSubmitterDid, + dest: unqualifiedDid, + verkey: signingKey.publicKeyBase58, + alias, + }) - const response = await pool.submitWriteRequest(agentContext, request, signingKey) + const response = await pool.submitWriteRequest(agentContext, request, submitterSigningKey) - agentContext.config.logger.debug(`Registered public did '${targetDid}' on ledger '${pool.indyNamespace}'`, { + agentContext.config.logger.debug(`Registered public did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, { response, }) - return targetDid + return } catch (error) { agentContext.config.logger.error( - `Error registering public did '${targetDid}' on ledger '${pool.indyNamespace}'`, + `Error registering public did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, { error, - submitterDid, - targetDid, - verkey, + unqualifiedSubmitterDid, + unqualifiedDid, + signingKey, alias, role, pool: pool.indyNamespace, @@ -270,53 +320,55 @@ export class IndyVdrIndyDidRegistrar implements DidRegistrar { private async setEndpointsForDid( agentContext: AgentContext, pool: IndyVdrPool, - submitterVerkey: string, - did: string, + unqualifiedDid: string, + signingKey: Key, endpoints: IndyEndpointAttrib ): Promise { try { - agentContext.config.logger.debug(`Set endpoints for did '${did}' on ledger '${pool.indyNamespace}'`, endpoints) + agentContext.config.logger.debug( + `Set endpoints for did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, + endpoints + ) const request = new AttribRequest({ - submitterDid: did, - targetDid: did, + submitterDid: unqualifiedDid, + targetDid: unqualifiedDid, raw: JSON.stringify({ endpoint: endpoints }), }) - const signingKey = Key.fromPublicKeyBase58(submitterVerkey, KeyType.Ed25519) - const response = await pool.submitWriteRequest(agentContext, request, signingKey) agentContext.config.logger.debug( - `Successfully set endpoints for did '${did}' on ledger '${pool.indyNamespace}'`, + `Successfully set endpoints for did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, { response, endpoints, } ) } catch (error) { - agentContext.config.logger.error(`Error setting endpoints for did '${did}' on ledger '${pool.indyNamespace}'`, { - error, - did, - endpoints, - }) + agentContext.config.logger.error( + `Error setting endpoints for did '${unqualifiedDid}' on ledger '${pool.indyNamespace}'`, + { + error, + unqualifiedDid, + endpoints, + } + ) throw new IndyVdrError(error) } } } -export interface IndyVdrDidCreateOptions extends DidCreateOptions { - method: 'indy' - did?: string +interface IndyVdrDidCreateOptionsBase extends DidCreateOptions { didDocument?: never // Not yet supported options: { alias?: string role?: string services?: DidDocumentService[] useEndpointAttrib?: boolean - submitterDid: string - submitterVerkey: string verkey?: string + + submitterDid: string } secret?: { seed?: Buffer @@ -324,6 +376,14 @@ export interface IndyVdrDidCreateOptions extends DidCreateOptions { } } -// TODO: Add Update and Deactivate -export type IndyVdrIndyDidUpdateOptions = never -export type IndyVdrIndyDidDeactivateOptions = never +interface IndyVdrDidCreateOptionsWithDid extends IndyVdrDidCreateOptionsBase { + method?: never + did: string +} + +interface IndyVdrDidCreateOptionsWithoutDid extends IndyVdrDidCreateOptionsBase { + method: 'indy' + did?: never +} + +export type IndyVdrDidCreateOptions = IndyVdrDidCreateOptionsWithDid | IndyVdrDidCreateOptionsWithoutDid diff --git a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts index 6f6d40cbcf..124e5da88e 100644 --- a/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts +++ b/packages/indy-vdr/src/dids/IndyVdrIndyDidResolver.ts @@ -1,4 +1,5 @@ -import type { CommEndpointType, GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' +import type { GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' +import type { IndyVdrPool } from '../pool' import type { DidResolutionResult, DidResolver, AgentContext } from '@aries-framework/core' import { GetAttribRequest, GetNymRequest } from '@hyperledger/indy-vdr-shared' @@ -15,10 +16,8 @@ export class IndyVdrIndyDidResolver implements DidResolver { public async resolve(agentContext: AgentContext, did: string): Promise { const didDocumentMetadata = {} try { - const nym = await this.getPublicDid(agentContext, did) - // Get DID Document from Get NYM response - const didDocument = await this.buildDidDocument(agentContext, nym, did) + const didDocument = await this.buildDidDocument(agentContext, did) return { didDocument, @@ -37,34 +36,37 @@ export class IndyVdrIndyDidResolver implements DidResolver { } } - private async buildDidDocument(agentContext: AgentContext, getNymResponseData: GetNymResponseData, did: string) { + private async buildDidDocument(agentContext: AgentContext, did: string) { + const { namespaceIdentifier, namespace } = parseIndyDid(did) + + const poolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) + const pool = poolService.getPoolForNamespace(namespace) + + const nym = await this.getPublicDid(pool, namespaceIdentifier) + // Create base Did Document // For modern did:indy DIDs, we assume that GET_NYM is always a full verkey in base58. // For backwards compatibility, we accept a shortened verkey and convert it using previous convention - const verkey = getFullVerkey(did, getNymResponseData.verkey) + const verkey = getFullVerkey(namespaceIdentifier, nym.verkey) const builder = indyDidDocumentFromDid(did, verkey) // If GET_NYM does not return any diddocContent, fallback to legacy GET_ATTRIB endpoint - if (!getNymResponseData.diddocContent) { + if (!nym.diddocContent) { const keyAgreementId = `${did}#key-agreement-1` - - const endpoints = await this.getEndpointsForDid(agentContext, did) + const endpoints = await this.getEndpointsForDid(agentContext, pool, namespaceIdentifier) if (endpoints) { - // If there is at least a didcomm endpoint, generate and a key agreement key - const commTypes: CommEndpointType[] = ['endpoint', 'did-communication', 'DIDComm'] - if (commTypes.some((type) => endpoints.types?.includes(type))) { - builder - .addVerificationMethod({ - controller: did, - id: keyAgreementId, - publicKeyBase58: createKeyAgreementKey(getNymResponseData.verkey), - type: 'X25519KeyAgreementKey2019', - }) - .addKeyAgreement(keyAgreementId) - } + builder + .addContext('https://w3id.org/security/suites/x25519-2019/v1') + .addVerificationMethod({ + controller: did, + id: keyAgreementId, + publicKeyBase58: createKeyAgreementKey(verkey), + type: 'X25519KeyAgreementKey2019', + }) + .addKeyAgreement(keyAgreementId) // Process endpoint attrib following the same rules as for did:sov addServicesFromEndpointsAttrib(builder, did, endpoints, keyAgreementId) @@ -72,41 +74,29 @@ export class IndyVdrIndyDidResolver implements DidResolver { return builder.build() } else { // Combine it with didDoc (TODO: Check if diddocContent is returned as a JSON object or a string) - return combineDidDocumentWithJson(builder.build(), getNymResponseData.diddocContent) + return combineDidDocumentWithJson(builder.build(), nym.diddocContent) } } - private async getPublicDid(agentContext: AgentContext, did: string) { - const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const { namespace, id } = parseIndyDid(did) - - const pool = indyVdrPoolService.getPoolForNamespace(namespace) - - const request = new GetNymRequest({ dest: id }) + private async getPublicDid(pool: IndyVdrPool, unqualifiedDid: string) { + const request = new GetNymRequest({ dest: unqualifiedDid }) const didResponse = await pool.submitReadRequest(request) if (!didResponse.result.data) { - throw new IndyVdrNotFoundError(`DID ${id} not found in indy namespace ${namespace}`) + throw new IndyVdrNotFoundError(`DID ${unqualifiedDid} not found in indy namespace ${pool.indyNamespace}`) } return JSON.parse(didResponse.result.data) as GetNymResponseData } - private async getEndpointsForDid(agentContext: AgentContext, did: string) { - const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const { namespace, id } = parseIndyDid(did) - - const pool = indyVdrPoolService.getPoolForNamespace(namespace) - + private async getEndpointsForDid(agentContext: AgentContext, pool: IndyVdrPool, unqualifiedDid: string) { try { - agentContext.config.logger.debug(`Get endpoints for did '${id}' from ledger '${pool.indyNamespace}'`) + agentContext.config.logger.debug(`Get endpoints for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`) - const request = new GetAttribRequest({ targetDid: id, raw: 'endpoint' }) + const request = new GetAttribRequest({ targetDid: unqualifiedDid, raw: 'endpoint' }) agentContext.config.logger.debug( - `Submitting get endpoint ATTRIB request for did '${id}' to ledger '${pool.indyNamespace}'` + `Submitting get endpoint ATTRIB request for did '${unqualifiedDid}' to ledger '${pool.indyNamespace}'` ) const response = await pool.submitReadRequest(request) @@ -116,7 +106,7 @@ export class IndyVdrIndyDidResolver implements DidResolver { const endpoints = JSON.parse(response.result.data as string)?.endpoint as IndyEndpointAttrib agentContext.config.logger.debug( - `Got endpoints '${JSON.stringify(endpoints)}' for did '${did}' from ledger '${pool.indyNamespace}'`, + `Got endpoints '${JSON.stringify(endpoints)}' for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`, { response, endpoints, @@ -126,7 +116,7 @@ export class IndyVdrIndyDidResolver implements DidResolver { return endpoints } catch (error) { agentContext.config.logger.error( - `Error retrieving endpoints for did '${did}' from ledger '${pool.indyNamespace}'`, + `Error retrieving endpoints for did '${unqualifiedDid}' from ledger '${pool.indyNamespace}'`, { error, } diff --git a/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts b/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts index 842707aaa1..dd4ecab222 100644 --- a/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts +++ b/packages/indy-vdr/src/dids/IndyVdrSovDidResolver.ts @@ -1,4 +1,5 @@ import type { GetNymResponseData, IndyEndpointAttrib } from './didSovUtil' +import type { IndyVdrPool } from '../pool' import type { DidResolutionResult, ParsedDid, DidResolver, AgentContext } from '@aries-framework/core' import { GetAttribRequest, GetNymRequest } from '@hyperledger/indy-vdr-shared' @@ -15,14 +16,19 @@ export class IndyVdrSovDidResolver implements DidResolver { const didDocumentMetadata = {} try { - const nym = await this.getPublicDid(agentContext, parsed.id) - const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - const endpoints = await this.getEndpointsForDid(agentContext, parsed.id) + // FIXME: this actually fetches the did twice (if not cached), once for the pool and once for the nym + // we do not store the diddocContent in the pool cache currently so we need to fetch it again + // The logic is mostly to determine which pool to use for a did + const { pool } = await indyVdrPoolService.getPoolForDid(agentContext, parsed.id) + const nym = await this.getPublicDid(pool, parsed.id) + const endpoints = await this.getEndpointsForDid(agentContext, pool, parsed.id) - if (endpoints) { - const keyAgreementId = `${parsed.did}#key-agreement-1` + const keyAgreementId = `${parsed.did}#key-agreement-1` + const builder = sovDidDocumentFromDid(parsed.did, nym.verkey) + if (endpoints) { addServicesFromEndpointsAttrib(builder, parsed.did, endpoints, keyAgreementId) } @@ -43,26 +49,17 @@ export class IndyVdrSovDidResolver implements DidResolver { } } - private async getPublicDid(agentContext: AgentContext, did: string) { - const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) - - const request = new GetNymRequest({ dest: did }) - + private async getPublicDid(pool: IndyVdrPool, unqualifiedDid: string) { + const request = new GetNymRequest({ dest: unqualifiedDid }) const didResponse = await pool.submitReadRequest(request) if (!didResponse.result.data) { - throw new IndyVdrNotFoundError(`DID ${did} not found`) + throw new IndyVdrNotFoundError(`DID ${unqualifiedDid} not found`) } return JSON.parse(didResponse.result.data) as GetNymResponseData } - private async getEndpointsForDid(agentContext: AgentContext, did: string) { - const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - - const pool = await indyVdrPoolService.getPoolForDid(agentContext, did) - + private async getEndpointsForDid(agentContext: AgentContext, pool: IndyVdrPool, did: string) { try { agentContext.config.logger.debug(`Get endpoints for did '${did}' from ledger '${pool.indyNamespace}'`) diff --git a/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts b/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts new file mode 100644 index 0000000000..e75cc4d97e --- /dev/null +++ b/packages/indy-vdr/src/dids/__tests__/IndyVdrIndyDidRegistrar.test.ts @@ -0,0 +1,706 @@ +import type { DidRecord, RecordSavedEvent } from '@aries-framework/core' + +import { + DidCommV1Service, + DidCommV2Service, + DidDocumentService, + DidDocument, + DidDocumentRole, + DidRepository, + DidsApi, + EventEmitter, + JsonTransformer, + Key, + KeyType, + RepositoryEventTypes, + SigningProviderRegistry, + TypedArrayEncoder, + VerificationMethod, +} from '@aries-framework/core' +import { Subject } from 'rxjs' + +import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService' +import { agentDependencies, getAgentConfig, getAgentContext, indySdk, mockProperty } from '../../../../core/tests' +import { IndySdkWallet } from '../../../../indy-sdk/src' +import { IndyVdrPool, IndyVdrPoolService } from '../../pool' +import { IndyVdrIndyDidRegistrar } from '../IndyVdrIndyDidRegistrar' + +jest.mock('../../pool/IndyVdrPool') +const IndyVdrPoolMock = IndyVdrPool as jest.Mock +const poolMock = new IndyVdrPoolMock() +mockProperty(poolMock, 'indyNamespace', 'ns1') + +const agentConfig = getAgentConfig('IndyVdrIndyDidRegistrar') + +const wallet = new IndySdkWallet(indySdk, agentConfig.logger, new SigningProviderRegistry([])) + +jest + .spyOn(wallet, 'createKey') + .mockResolvedValue(Key.fromPublicKeyBase58('E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', KeyType.Ed25519)) +const storageService = new InMemoryStorageService() +const eventEmitter = new EventEmitter(agentDependencies, new Subject()) +const didRepository = new DidRepository(storageService, eventEmitter) + +const agentContext = getAgentContext({ + wallet, + registerInstances: [ + [DidRepository, didRepository], + [IndyVdrPoolService, { getPoolForNamespace: jest.fn().mockReturnValue(poolMock) }], + [ + DidsApi, + { + resolve: jest.fn().mockResolvedValue({ + didDocument: new DidDocument({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + authentication: [ + new VerificationMethod({ + id: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }), + ], + }), + }), + }, + ], + ], + agentConfig, +}) + +const indyVdrIndyDidRegistrar = new IndyVdrIndyDidRegistrar() + +describe('IndyVdrIndyDidRegistrar', () => { + afterEach(() => { + jest.clearAllMocks() + }) + + test('returns an error state if both did and privateKey are provided', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:did-value', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + secret: { + privateKey: TypedArrayEncoder.fromString('key'), + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `Only one of 'seed', 'privateKey' and 'did' must be provided`, + }, + }) + }) + + test('returns an error state if the submitter did is not a valid did:indy did', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + submitterDid: 'BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but it is not a valid did:indy did', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'unknownError: BzCbsNYhMrjHiqZDTUASHg is not a valid did:indy did', + }, + }) + }) + + test('returns an error state if did is provided, but no verkey', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'If a did is defined, a matching verkey must be provided', + }, + }) + }) + + test('returns an error state if did and verkey are provided, but the did is not self certifying', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'verkey', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: 'Initial verkey verkey does not match did did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + }, + }) + }) + + test('returns an error state if did is provided, but does not match with the namespace from the submitterDid', async () => { + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool2:B6xaJg1c2xU3D9ppCtt1CZ', + options: { + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + }, + }) + + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: + 'The submitter did uses namespace pool1 and the did to register uses namespace pool2. Namespaces must match.', + }, + }) + }) + + test('creates a did:indy document without services', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: { + privateKey, + }, + }) + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'B6xaJg1c2xU3D9ppCtt1CZ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD', + undefined + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('creates a did:indy document by passing did', async () => { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + options: { + verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + }, + secret: {}, + }) + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'B6xaJg1c2xU3D9ppCtt1CZ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD', + undefined + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: undefined, + }, + secret: {}, + }, + }) + }) + + test('creates a did:indy document with services using diddocContent', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'B6xaJg1c2xU3D9ppCtt1CZ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD', + { + '@context': [], + authentication: [], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + accept: ['didcomm/aip2;env=rfc19'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + }, + { + accept: ['didcomm/v2'], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + }, + ], + verificationMethod: [ + { + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + type: 'X25519KeyAgreementKey2019', + }, + ], + } + ) + expect(setEndpointsForDidSpy).not.toHaveBeenCalled() + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('creates a did:indy document with services using attrib', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const result = await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + useEndpointAttrib: true, + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(registerPublicDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + // Unqualified submitter did + 'BzCbsNYhMrjHiqZDTUASHg', + // submitter signing key, + expect.any(Key), + // Unqualified created indy did + 'B6xaJg1c2xU3D9ppCtt1CZ', + // Verkey + expect.any(Key), + // Alias + 'Hello', + // Role + 'STEWARD' + ) + expect(setEndpointsForDidSpy).toHaveBeenCalledWith( + agentContext, + poolMock, + 'B6xaJg1c2xU3D9ppCtt1CZ', + expect.any(Key), + { + endpoint: 'https://example.com/endpoint', + routingKeys: ['key-1'], + types: ['endpoint', 'did-communication', 'DIDComm'], + } + ) + expect(JsonTransformer.toJSON(result)).toMatchObject({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'finished', + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + verificationMethod: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey', + type: 'Ed25519VerificationKey2018', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1', + type: 'X25519KeyAgreementKey2019', + controller: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + publicKeyBase58: 'Fbv17ZbnUSbafsiUBJbdGeC62M8v8GEscVMMcE59mRPt', + }, + ], + service: [ + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#endpoint', + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#did-communication', + serviceEndpoint: 'https://example.com/endpoint', + type: 'did-communication', + priority: 0, + recipientKeys: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + routingKeys: ['key-1'], + accept: ['didcomm/aip2;env=rfc19'], + }, + { + id: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#didcomm-1', + serviceEndpoint: 'https://example.com/endpoint', + type: 'DIDComm', + routingKeys: ['key-1'], + accept: ['didcomm/v2'], + }, + ], + authentication: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#verkey'], + assertionMethod: undefined, + keyAgreement: ['did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ#key-agreement-1'], + }, + secret: { + privateKey, + }, + }, + }) + }) + + test('stores the did document', async () => { + const privateKey = TypedArrayEncoder.fromString('96213c3d7fc8d4d6754c712fd969598e') + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const registerPublicDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'registerPublicDid') + registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve()) + + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - method is private + const setEndpointsForDidSpy = jest.spyOn(indyVdrIndyDidRegistrar, 'setEndpointsForDid') + setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined)) + + const saveCalled = jest.fn() + eventEmitter.on>(RepositoryEventTypes.RecordSaved, saveCalled) + + await indyVdrIndyDidRegistrar.create(agentContext, { + method: 'indy', + options: { + alias: 'Hello', + submitterDid: 'did:indy:pool1:BzCbsNYhMrjHiqZDTUASHg', + role: 'STEWARD', + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'https://example.com/endpoint', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['key-1'], + serviceEndpoint: 'https://example.com/endpoint', + }), + ], + }, + secret: { + privateKey, + }, + }) + + expect(saveCalled).toHaveBeenCalledTimes(1) + const [saveEvent] = saveCalled.mock.calls[0] + + expect(saveEvent.payload.record).toMatchObject({ + did: 'did:indy:pool1:B6xaJg1c2xU3D9ppCtt1CZ', + role: DidDocumentRole.Created, + _tags: { + recipientKeyFingerprints: ['z6LSrH6AdsQeZuKKmG6Ehx7abEQZsVg2psR2VU536gigUoAe'], + }, + didDocument: undefined, + }) + }) + + test('returns an error state when calling update', async () => { + const result = await indyVdrIndyDidRegistrar.update() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: updating did:indy not implemented yet`, + }, + }) + }) + + test('returns an error state when calling deactivate', async () => { + const result = await indyVdrIndyDidRegistrar.deactivate() + + expect(result).toEqual({ + didDocumentMetadata: {}, + didRegistrationMetadata: {}, + didState: { + state: 'failed', + reason: `notImplemented: deactivating did:indy not implemented yet`, + }, + }) + }) +}) diff --git a/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts b/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts index c8001ccd19..0ed14f5856 100644 --- a/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts +++ b/packages/indy-vdr/src/dids/__tests__/IndyVdrSovDidResolver.test.ts @@ -18,7 +18,7 @@ const agentConfig = getAgentConfig('IndyVdrSovDidResolver') const agentContext = getAgentContext({ agentConfig, - registerInstances: [[IndyVdrPoolService, { getPoolForDid: jest.fn().mockReturnValue(poolMock) }]], + registerInstances: [[IndyVdrPoolService, { getPoolForDid: jest.fn().mockReturnValue({ pool: poolMock }) }]], }) const resolver = new IndyVdrSovDidResolver() diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json index 56014e70be..68874b6fc2 100644 --- a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyR1xKJw17sUoXhejEpugMYJ.json @@ -1,5 +1,5 @@ { - "@context": ["https://w3id.org/did/v1"], + "@context": ["https://w3id.org/did/v1", "https://w3id.org/security/suites/ed25519-2018/v1"], "id": "did:indy:ns1:R1xKJw17sUoXhejEpugMYJ", "verificationMethod": [ { diff --git a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json index c131549e18..2a58c356ca 100644 --- a/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json +++ b/packages/indy-vdr/src/dids/__tests__/__fixtures__/didIndyWJz9mHyW9BZksioQnRsrAo.json @@ -1,5 +1,10 @@ { - "@context": ["https://w3id.org/did/v1", "https://didcomm.org/messaging/contexts/v2"], + "@context": [ + "https://w3id.org/did/v1", + "https://w3id.org/security/suites/ed25519-2018/v1", + "https://w3id.org/security/suites/x25519-2019/v1", + "https://didcomm.org/messaging/contexts/v2" + ], "id": "did:indy:ns1:WJz9mHyW9BZksioQnRsrAo", "verificationMethod": [ { diff --git a/packages/indy-vdr/src/dids/didIndyUtil.ts b/packages/indy-vdr/src/dids/didIndyUtil.ts index 6644703169..e91f5ebd2b 100644 --- a/packages/indy-vdr/src/dids/didIndyUtil.ts +++ b/packages/indy-vdr/src/dids/didIndyUtil.ts @@ -1,8 +1,12 @@ +import type { AgentContext } from '@aries-framework/core' + import { + getKeyFromVerificationMethod, AriesFrameworkError, convertPublicKeyToX25519, DidDocument, DidDocumentBuilder, + DidsApi, Hasher, JsonTransformer, Key, @@ -19,6 +23,7 @@ export function indyDidDocumentFromDid(did: string, verKeyBase58: string) { const publicKeyBase58 = verKeyBase58 const builder = new DidDocumentBuilder(did) + .addContext('https://w3id.org/security/suites/ed25519-2018/v1') .addVerificationMethod({ controller: did, id: verificationMethodId, @@ -37,8 +42,8 @@ export function createKeyAgreementKey(verkey: string) { export function parseIndyDid(did: string) { const match = did.match(DID_INDY_REGEX) if (match) { - const [, namespace, id] = match - return { namespace, id } + const [, namespace, namespaceIdentifier] = match + return { namespace, namespaceIdentifier } } else { throw new AriesFrameworkError(`${did} is not a valid did:indy did`) } @@ -163,3 +168,27 @@ export function indyDidFromNamespaceAndInitialKey(namespace: string, initialKey: return { did, id, verkey } } + +/** + * Fetches the verification key for a given did:indy did and returns the key as a {@link Key} object. + * + * @throws {@link AriesFrameworkError} if the did could not be resolved or the key could not be extracted + */ +export async function verificationKeyForIndyDid(agentContext: AgentContext, did: string) { + // FIXME: we should store the didDocument in the DidRecord so we don't have to fetch our own did + // from the ledger to know which key is associated with the did + const didsApi = agentContext.dependencyManager.resolve(DidsApi) + const didResult = await didsApi.resolve(did) + + if (!didResult.didDocument) { + throw new AriesFrameworkError( + `Could not resolve did ${did}. ${didResult.didResolutionMetadata.error} ${didResult.didResolutionMetadata.message}` + ) + } + + // did:indy dids MUST have a verificationMethod with #verkey + const verificationMethod = didResult.didDocument.dereferenceKey(`${did}#verkey`) + const key = getKeyFromVerificationMethod(verificationMethod) + + return key +} diff --git a/packages/indy-vdr/src/dids/didSovUtil.ts b/packages/indy-vdr/src/dids/didSovUtil.ts index b836eb2eae..0517d00315 100644 --- a/packages/indy-vdr/src/dids/didSovUtil.ts +++ b/packages/indy-vdr/src/dids/didSovUtil.ts @@ -123,14 +123,15 @@ export function endpointsAttribFromServices(services: DidDocumentService[]): Ind const commServiceType = commService.type as CommEndpointType if (types.includes(commServiceType)) { throw new AriesFrameworkError('Only a single communication service per type is supported') - } else { - types.push(commServiceType) } - if (commService instanceof DidCommV1Service || commService instanceof DidCommV2Service) { - if (commService.routingKeys) { - commService.routingKeys.forEach((item) => routingKeys.add(item)) - } + types.push(commServiceType) + + if ( + (commService instanceof DidCommV1Service || commService instanceof DidCommV2Service) && + commService.routingKeys + ) { + commService.routingKeys.forEach((item) => routingKeys.add(item)) } } diff --git a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts index 1ad1b0f80a..d8e31f72a7 100644 --- a/packages/indy-vdr/src/pool/IndyVdrPoolService.ts +++ b/packages/indy-vdr/src/pool/IndyVdrPoolService.ts @@ -36,8 +36,15 @@ export class IndyVdrPoolService { * If the did is a qualified indy did, the pool will be determined based on the namespace. * If it is a legacy unqualified indy did, the pool will be determined based on the algorithm as described in this document: * https://docs.google.com/document/d/109C_eMsuZnTnYe2OAd02jAts1vC4axwEKIq7_4dnNVA/edit + * + * This method will optionally return a nym response when the did has been resolved to determine the ledger + * either now or in the past. The nymResponse can be used to prevent multiple ledger quries fetching the same + * did */ - public async getPoolForDid(agentContext: AgentContext, did: string): Promise { + public async getPoolForDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndyVdrPool; nymResponse?: CachedDidResponse['nymResponse'] }> { // Check if the did starts with did:indy const match = did.match(DID_INDY_REGEX) @@ -46,7 +53,7 @@ export class IndyVdrPoolService { const pool = this.getPoolForNamespace(namespace) - if (pool) return pool + if (pool) return { pool } throw new IndyVdrError(`Pool for indy namespace '${namespace}' not found`) } else { @@ -54,7 +61,10 @@ export class IndyVdrPoolService { } } - private async getPoolForLegacyDid(agentContext: AgentContext, did: string): Promise { + private async getPoolForLegacyDid( + agentContext: AgentContext, + did: string + ): Promise<{ pool: IndyVdrPool; nymResponse?: CachedDidResponse['nymResponse'] }> { const pools = this.pools if (pools.length === 0) { @@ -71,7 +81,7 @@ export class IndyVdrPoolService { // If we have the nym response with associated pool in the cache, we'll use that if (cachedNymResponse && pool) { this.logger.trace(`Found ledger id '${pool.indyNamespace}' for did '${did}' in cache`) - return pool + return { pool, nymResponse: cachedNymResponse.nymResponse } } const { successful, rejected } = await this.getSettledDidResponsesFromPools(did, pools) @@ -119,7 +129,7 @@ export class IndyVdrPoolService { }, indyNamespace: value.did.indyNamespace, }) - return value.pool + return { pool: value.pool, nymResponse: value.did.nymResponse } } private async getSettledDidResponsesFromPools(did: string, pools: IndyVdrPool[]) { @@ -159,7 +169,7 @@ export class IndyVdrPoolService { private async getDidFromPool(did: string, pool: IndyVdrPool): Promise { try { this.logger.trace(`Get public did '${did}' from ledger '${pool.indyNamespace}'`) - const request = await new GetNymRequest({ dest: did }) + const request = new GetNymRequest({ dest: did }) this.logger.trace(`Submitting get did request for did '${did}' to ledger '${pool.indyNamespace}'`) const response = await pool.submitReadRequest(request) diff --git a/packages/indy-vdr/tests/__fixtures__/anoncreds.ts b/packages/indy-vdr/tests/__fixtures__/anoncreds.ts new file mode 100644 index 0000000000..fea36d5fcb --- /dev/null +++ b/packages/indy-vdr/tests/__fixtures__/anoncreds.ts @@ -0,0 +1,30 @@ +export const credentialDefinitionValue = { + primary: { + n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', + s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', + r: { + master_secret: + '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', + age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', + }, + rctxt: + '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', + z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', + }, + revocation: { + g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + g_dash: + '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + htilde: + '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + h_cap: + '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', + y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', + }, +} diff --git a/packages/indy-vdr/tests/helpers.ts b/packages/indy-vdr/tests/helpers.ts index ecaf154ee9..2ea2390329 100644 --- a/packages/indy-vdr/tests/helpers.ts +++ b/packages/indy-vdr/tests/helpers.ts @@ -1,12 +1,10 @@ -import type { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' -import type { AgentContext, Key } from '@aries-framework/core' +import type { IndyVdrDidCreateOptions } from '../src/dids/IndyVdrIndyDidRegistrar' +import type { Agent } from '@aries-framework/core' -import { KeyType } from '@aries-framework/core' -import { AttribRequest, NymRequest } from '@hyperledger/indy-vdr-shared' +import { DidCommV1Service, DidCommV2Service, DidDocumentService, KeyType } from '@aries-framework/core' import { genesisTransactions } from '../../core/tests/helpers' import { IndyVdrModuleConfig } from '../src/IndyVdrModuleConfig' -import { indyDidFromPublicKeyBase58 } from '../src/utils/did' export const indyVdrModuleConfig = new IndyVdrModuleConfig({ networks: [ @@ -19,38 +17,46 @@ export const indyVdrModuleConfig = new IndyVdrModuleConfig({ ], }) -export async function createDidOnLedger( - indyVdrPoolService: IndyVdrPoolService, - agentContext: AgentContext, - submitterDid: string, - signerKey: Key -) { - const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') - - const key = await agentContext.wallet.createKey({ keyType: KeyType.Ed25519 }) - const did = indyDidFromPublicKeyBase58(key.publicKeyBase58) - - const nymRequest = new NymRequest({ - dest: did, - submitterDid, - verkey: key.publicKeyBase58, - }) - - await pool.submitWriteRequest(agentContext, nymRequest, signerKey) - - const attribRequest = new AttribRequest({ - submitterDid: did, - targetDid: did, - raw: JSON.stringify({ - endpoint: { - endpoint: 'https://agent.com', - types: ['endpoint', 'did-communication', 'DIDComm'], - routingKeys: ['routingKey1', 'routingKey2'], - }, - }), +export async function createDidOnLedger(agent: Agent, submitterDid: string) { + const key = await agent.wallet.createKey({ keyType: KeyType.Ed25519 }) + + const createResult = await agent.dids.create({ + method: 'indy', + options: { + submitterDid, + alias: 'Alias', + role: 'TRUSTEE', + verkey: key.publicKeyBase58, + useEndpointAttrib: true, + services: [ + new DidDocumentService({ + id: `#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }), + new DidCommV1Service({ + id: `#did-communication`, + priority: 0, + recipientKeys: [`#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + accept: ['didcomm/aip2;env=rfc19'], + }), + new DidCommV2Service({ + accept: ['didcomm/v2'], + id: `#didcomm-1`, + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + }), + ], + }, }) - await pool.submitWriteRequest(agentContext, attribRequest, key) + if (!createResult.didState.did) { + throw new Error( + `Did was not created. ${createResult.didState.state === 'failed' ? createResult.didState.reason : 'Not finished'}` + ) + } - return { did, key } + return { did: createResult.didState.did, key } } diff --git a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts index 83f3323c54..f3448d169a 100644 --- a/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-anoncreds-registry.e2e.test.ts @@ -9,36 +9,35 @@ import { } from '../../core/tests/helpers' import { IndySdkModule } from '../../indy-sdk/src' import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' -import { IndyVdrSovDidResolver } from '../src' +import { IndyVdrIndyDidResolver, IndyVdrModule, IndyVdrSovDidResolver } from '../src' import { IndyVdrAnonCredsRegistry } from '../src/anoncreds/IndyVdrAnonCredsRegistry' import { IndyVdrPoolService } from '../src/pool' +import { credentialDefinitionValue } from './__fixtures__/anoncreds' import { indyVdrModuleConfig } from './helpers' const agentConfig = getAgentConfig('IndyVdrAnonCredsRegistry') -// TODO: update to module once available -const indyVdrPoolService = new IndyVdrPoolService(agentConfig.logger, indyVdrModuleConfig) -const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') - -// Verkey for the publicDidSeed -const signingKey = Key.fromPublicKeyBase58('FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', KeyType.Ed25519) const indyVdrAnonCredsRegistry = new IndyVdrAnonCredsRegistry() const agent = new Agent({ config: agentConfig, dependencies: agentDependencies, modules: { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + }), indySdk: new IndySdkModule({ indySdk, }), dids: new DidsModule({ - resolvers: [new IndyVdrSovDidResolver()], + resolvers: [new IndyVdrSovDidResolver(), new IndyVdrIndyDidResolver()], }), }, }) -agent.dependencyManager.registerInstance(IndyVdrPoolService, indyVdrPoolService) +const indyVdrPoolService = agent.dependencyManager.resolve(IndyVdrPoolService) +const pool = indyVdrPoolService.getPoolForNamespace('pool:localtest') describe('IndyVdrAnonCredsRegistry', () => { beforeAll(async () => { @@ -60,13 +59,18 @@ describe('IndyVdrAnonCredsRegistry', () => { test('register and resolve a schema and credential definition', async () => { const dynamicVersion = `1.${Math.random() * 100}` + const legacyIssuerId = 'TL1EaPFCZ8Si5aUrqScBDt' + const signingKey = Key.fromPublicKeyBase58('FMGcFuU3QwAQLywxvmEnSorQT3NwU9wgDMMTaDFtvswm', KeyType.Ed25519) + const didIndyIssuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + + const legacySchemaId = `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}` + const didIndySchemaId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/SCHEMA/test/${dynamicVersion}` + const schemaResult = await indyVdrAnonCredsRegistry.registerSchema(agent.context, { - options: { - didIndyNamespace: 'pool:localtest', - }, + options: {}, schema: { attrNames: ['age'], - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, name: 'test', version: dynamicVersion, }, @@ -77,31 +81,47 @@ describe('IndyVdrAnonCredsRegistry', () => { state: 'finished', schema: { attrNames: ['age'], - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, name: 'test', version: dynamicVersion, }, - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + schemaId: didIndySchemaId, }, registrationMetadata: {}, schemaMetadata: { indyLedgerSeqNo: expect.any(Number), + }, + }) + + // Wait some time before resolving credential definition object + await new Promise((res) => setTimeout(res, 1000)) + + const legacySchema = await indyVdrAnonCredsRegistry.getSchema(agent.context, legacySchemaId) + expect(legacySchema).toMatchObject({ + schema: { + attrNames: ['age'], + name: 'test', + version: dynamicVersion, + issuerId: legacyIssuerId, + }, + schemaId: legacySchemaId, + resolutionMetadata: {}, + schemaMetadata: { didIndyNamespace: 'pool:localtest', + indyLedgerSeqNo: expect.any(Number), }, }) - const schemaResponse = await indyVdrAnonCredsRegistry.getSchema( - agent.context, - schemaResult.schemaState.schemaId as string - ) - expect(schemaResponse).toMatchObject({ + // Resolve using did indy schema id + const didIndySchema = await indyVdrAnonCredsRegistry.getSchema(agent.context, didIndySchemaId) + expect(didIndySchema).toMatchObject({ schema: { attrNames: ['age'], name: 'test', version: dynamicVersion, - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, }, - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + schemaId: didIndySchemaId, resolutionMetadata: {}, schemaMetadata: { didIndyNamespace: 'pool:localtest', @@ -109,137 +129,72 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }) + const legacyCredentialDefinitionId = `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG` + const didIndyCredentialDefinitionId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/CLAIM_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG` const credentialDefinitionResult = await indyVdrAnonCredsRegistry.registerCredentialDefinition(agent.context, { credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, tag: 'TAG', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + schemaId: didIndySchemaId, type: 'CL', - value: { - primary: { - n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', - s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', - r: { - master_secret: - '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', - age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', - }, - rctxt: - '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', - z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', - }, - revocation: { - g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - g_dash: - '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - htilde: - '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h_cap: - '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - }, - }, - }, - options: { - didIndyNamespace: 'pool:localtest', + value: credentialDefinitionValue, }, + options: {}, }) expect(credentialDefinitionResult).toMatchObject({ - credentialDefinitionMetadata: { - didIndyNamespace: 'pool:localtest', - }, + credentialDefinitionMetadata: {}, credentialDefinitionState: { credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, tag: 'TAG', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + schemaId: didIndySchemaId, type: 'CL', - value: { - primary: { - n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', - s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', - r: { - master_secret: - '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', - age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', - }, - rctxt: - '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', - z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', - }, - revocation: { - g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - g_dash: - '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - htilde: - '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h_cap: - '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - }, - }, + value: credentialDefinitionValue, }, - credentialDefinitionId: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG`, + credentialDefinitionId: didIndyCredentialDefinitionId, state: 'finished', }, registrationMetadata: {}, }) - const credentialDefinitionResponse = await indyVdrAnonCredsRegistry.getCredentialDefinition( + // Wait some time before resolving credential definition object + await new Promise((res) => setTimeout(res, 1000)) + + const legacyCredentialDefinition = await indyVdrAnonCredsRegistry.getCredentialDefinition( agent.context, - credentialDefinitionResult.credentialDefinitionState.credentialDefinitionId as string + legacyCredentialDefinitionId ) - expect(credentialDefinitionResponse).toMatchObject({ - credentialDefinitionId: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG`, + expect(legacyCredentialDefinition).toMatchObject({ + credentialDefinitionId: legacyCredentialDefinitionId, credentialDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', - schemaId: `TL1EaPFCZ8Si5aUrqScBDt:2:test:${dynamicVersion}`, + issuerId: legacyIssuerId, + schemaId: legacySchemaId, tag: 'TAG', type: 'CL', - value: { - primary: { - n: '96517142458750088826087901549537285521906361834839650465292394026155791790248920518228426560592477800345470631128393537910767968076647428853737338120375137978526133371095345886547568849980095910835456337942570110635942227498396677781945046904040000347997661394155645138402989185582727368743644878567330299129483548946710969360956979880962101169330048328620192831242584775824654760726417810662811409929761424969870024291961980782988854217354212087291593903213167261779548063894662259300608395552269380441482047725811646638173390809967510159302372018819245039226007682154490256871635806558216146474297742733244470144481', - s: '20992997088800769394205042281221010730843336204635587269131066142238627416871294692123680065003125450990475247419429111144686875080339959479648984195457400282722471552678361441816569115316390063503704185107464429408708889920969284364549487320740759452356010336698287092961864738455949515401889999320804333605635972368885179914619910494573144273759358510644118555354521660927445864167887629319425342133470781407706668100509422240127902573158722086763638357241708157836231326104213948080124231104027985997092193458353052131052627451830345602820935886233072722689872803371231173593216542422645374438328309647440653637339', - r: { - master_secret: - '96243300745227716230048295249700256382424379142767068560156597061550615821183969840133023439359733351013932957841392861447122785423145599004240865527901625751619237368187131360686977600247815596986496835118582544022443932674638843143227258367859921648385998241629365673854479167826898057354386557912400420925145402535066400276579674049751639901555837852972622061540154688641944145082381483273814616102862399655638465723909813901943343059991047747289931252070264205125933226649905593045675877143065756794349492159868513288280364195700788501708587588090219665708038121636837649207584981238653023213330207384929738192210', - age: '73301750658973501389860306433954162777688414647250690792688553201037736559940890441467927863421690990807820789906540409252803697381653459639864945429958798104818241892796218340966964349674689564019059435289373607451125919476002261041343187491848656595845611576458601110066647002078334660251906541846222115184239401618625285703919125402959929850028352261117167621349930047514115676870868726855651130262227714591240534532398809967792128535084773798290351459391475237061458901325844643172504167457543287673202618731404966555015061917662865397763636445953946274068384614117513804834235388565249331682010365807270858083546', - }, - rctxt: - '37788128721284563440858950515231840450431543928224096081933216180465915572829884228780081835462293611329848268384962871736884632087015070623933628853658097637604059748079512999518737243304794110313829761155878287344472916564970806851294430356498883927870926898737394894892797927804721407643833828162246495645836390303263072281761384240973982733122383052566872688887552226083782030670443318152427129452272570595367287061688769394567289624972332234661767648489253220495098949161964171486245324730862072203259801377135500275012560207100571502032523912388082460843991502336467718632746396226650194750972544436894286230063', - z: '43785356695890052462955676926428400928903479009358861113206349419200366390858322895540291303484939601128045362682307382393826375825484851021601464391509750565285197155653613669680662395620338416776539485377195826876505126073018100680273457526216247879013350460071029101583221000647494610122617904515744711339846577920055655093367012508192004131719432915903924789974568341538556528133188398290594619318653419602058489178526243446782729272985727332736198326183868783570550373552407121582843992983431205917273352230155794805507408743590383242904107596623095433284330566906935063373759426916339149701872288610119965287995', - }, - revocation: { - g: '1 0A84C28144BC8B677839038FFFA824AB5ADE517F8DD4A89F092FAF9A3560C62D 1 00FD708E112EEA5D89AF9D0559795E6DBCF56D3B8CDF79EFF34A72EB741F896F 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - g_dash: - '1 201F3E23CC7E9284F3EFCF9500F1E2537C398EAB2E94D2EB801AECC7FBFBDC01 1 08132C7723CF9861D4CC24B56555EF1CBD9AE746C97B3ADFA36C669F2DCE09B6 1 1B2397FB2A1ADE704E2A1E4C242612F4677F9F1BD09E6B14C2E77E25EDA4C62E 1 00CDC2CF5F278D699D52223577AB032C150A3CB4C8E8AB07AB9D592772910E95 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - h: '1 072E0A505004F2F32B4210E72FA18A2ADF17F31479BD2059B7A8C0BA58F2ACB3 1 05C70F039E60317003C41C319753ECACC629791FDB06D6ADC5B06DD94501B973 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h0: '1 03CBE26D18118E9770D4A0B3E8607B3B3A8D3D3CA81FF8D41862430CC583156E 1 004A2A57E0A826AEFF007EDDAF89B02F054050843689167B10127FE9EDEEEDA9 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h1: '1 10C9F9DE537994E4FEF2625AFA78342C8A096238A875F6899DD500230E6022E5 1 0C0A88F53D020557377B4ED9C3826E9B8F918DD03E23B0F8ECD922F8333359D3 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h2: '1 017F748AEEC1DDE4E4C3FBAE771C041F0A6FAEAF34FD02AF773AC4B75025147B 1 1298DBD9A4BEE6AD54E060A57BCE932735B7738C30A9ADAEFE2F38E1858A0183 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - htilde: - '1 0C471F0451D6AC352E28B6ECDE8D7233B75530AE59276DF0F4B9A8B0C5C7E5DB 1 24CE4461910AA5D60C09C24EE0FE51E1B1600D8BA6E483E9050EF897CA3E3C8A 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - h_cap: - '1 225B2106DEBD353AABDFC4C7F7E8660D308FB514EA9DAE0533DDEB65CF796159 1 1F6093622F439FC22C64F157F4F35F7C592EC0169C6F0026BC44CD3E375974A7 1 142126FAC3657AD846D394E1F72FD01ECC15E84416713CD133980E324B24F4BC 1 0357995DBDCD4385E59E607761AB30AE8D9DDE005A777EE846EF51AE2816CD33 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - u: '1 00D8DDC2EB6536CA320EE035D099937E59B11678162C1BFEB30C58FCA9F84650 1 1557A5B05A1A30D63322E187D323C9CA431BC5E811E68D4703933D9DDA26D299 1 10E8AB93AA87839B757521742EBA23C3B257C91F61A93D37AEC4C0A011B5F073 1 1DA65E40406A7875DA8CFCE9FD7F283145C166382A937B72819BDC335FE9A734 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - pk: '1 1A7EBBE3E7F8ED50959851364B20997944FA8AE5E3FC0A2BB531BAA17179D320 1 02C55FE6F64A2A4FF49B37C513C39E56ECD565CFAD6CA46DC6D8095179351863 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8', - y: '1 1BF97F07270EC21A89E43BCA645D86A755F846B547238F1DA379E088CDD9B40D 1 146BB00F56FFC0DEF6541CEB484C718559B398DB1547B52850E46B23144161F1 1 079A1BEF8DFFA4E6352F701D476664340E7FBE5D3F46B897412BD2B5F10E33D7 1 02FDC508AEF90FB11961AF332BE4037973C76B954FFA48848F7E0588E93FCA8C 2 095E45DDF417D05FB10933FFC63D474548B7FFFF7888802F07FFFFFF7D07A8A8 1 0000000000000000000000000000000000000000000000000000000000000000', - }, - }, + value: credentialDefinitionValue, + }, + credentialDefinitionMetadata: { + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) + + // resolve using did indy credential definition id + const didIndyCredentialDefinition = await indyVdrAnonCredsRegistry.getCredentialDefinition( + agent.context, + didIndyCredentialDefinitionId + ) + + expect(didIndyCredentialDefinition).toMatchObject({ + credentialDefinitionId: didIndyCredentialDefinitionId, + credentialDefinition: { + issuerId: didIndyIssuerId, + schemaId: didIndySchemaId, + tag: 'TAG', + type: 'CL', + value: credentialDefinitionValue, }, credentialDefinitionMetadata: { didIndyNamespace: 'pool:localtest', @@ -248,12 +203,13 @@ describe('IndyVdrAnonCredsRegistry', () => { }) // We don't support creating a revocation registry using AFJ yet, so we directly use indy-vdr to create the revocation registry - const revocationRegistryDefinitionId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` + const legacyRevocationRegistryId = `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResult.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag` + const didIndyRevocationRegistryId = `did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt/anoncreds/v0/REV_REG_DEF/${schemaResult.schemaMetadata.indyLedgerSeqNo}/TAG/tag` const revocationRegistryRequest = new RevocationRegistryDefinitionRequest({ submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', revocationRegistryDefinitionV1: { - credDefId: credentialDefinitionResponse.credentialDefinitionId, - id: revocationRegistryDefinitionId, + credDefId: legacyCredentialDefinitionId, + id: legacyRevocationRegistryId, revocDefType: 'CL_ACCUM', tag: 'tag', value: { @@ -277,7 +233,7 @@ describe('IndyVdrAnonCredsRegistry', () => { // Also create a revocation registry entry const revocationEntryRequest = new RevocationRegistryEntryRequest({ - revocationRegistryDefinitionId, + revocationRegistryDefinitionId: legacyRevocationRegistryId, revocationRegistryDefinitionType: 'CL_ACCUM', revocationRegistryEntry: { ver: '1.0', @@ -285,21 +241,50 @@ describe('IndyVdrAnonCredsRegistry', () => { accum: '1', }, }, - submitterDid: 'TL1EaPFCZ8Si5aUrqScBDt', + submitterDid: legacyIssuerId, }) // After this call we can query the revocation registry entries (using timestamp now) - const response = await pool.submitWriteRequest(agent.context, revocationEntryRequest, signingKey) + const entryResponse = await pool.submitWriteRequest(agent.context, revocationEntryRequest, signingKey) - const revocationRegistryDefintion = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( + const legacyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( agent.context, - revocationRegistryDefinitionId + legacyRevocationRegistryId ) + expect(legacyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: legacyRevocationRegistryId, + revocationRegistryDefinition: { + issuerId: legacyIssuerId, + revocDefType: 'CL_ACCUM', + value: { + maxCredNum: 100, + tailsHash: 'HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + tailsLocation: + '/var/folders/l3/xy8jzyvj4p5_d9g1123rt4bw0000gn/T/HLKresYcDSZYSKogq8wive4zyXNY84669MygftLFBG1i', + publicKeys: { + accumKey: { + z: '1 1812B206EB395D3AEBD4BBF53EBB0FFC3371D8BD6175316AB32C1C5F65452051 1 22A079D49C5351EFDC1410C81A1F6D8B2E3B79CFF20A30690C118FE2050F72CB 1 0FFC28B923A4654E261DB4CB5B9BABEFCB4DB189B20F52412B0CC9CCCBB8A3B2 1 1EE967C43EF1A3F487061D21B07076A26C126AAF7712E7B5CF5A53688DDD5CC0 1 009ED4D65879CA81DA8227D34CEA3B759B4627E1E2FFB273E9645CD4F3B10F19 1 1CF070212E1E213AEB472F56EDFC9D48009796C77B2D8CC16F2836E37B8715C2 1 04954F0B7B468781BAAE3291DD0E6FFA7F1AF66CAA4094D37B24363CC34606FB 1 115367CB755E9DB18781B3825CB1AEE2C334558B2C038E13DF57BB57CE1CF847 1 110D37EC05862EE2757A7DF39E814876FC97376FF8105D2D29619CB575537BDE 1 13C559A9563FCE083B3B39AE7E8FCA4099BEF3A4C8C6672E543D521F9DA88F96 1 137D87CC22ACC1B6B8C20EABE59F6ED456A58FE4CBEEFDFC4FA9B87E3EF32D17 1 00A2A9711737AAF0404F35AE502887AC6172B2B57D236BD4A40B45F659BFC696', + }, + }, + }, + tag: 'tag', + credDefId: legacyCredentialDefinitionId, + }, + revocationRegistryDefinitionMetadata: { + issuanceType: 'ISSUANCE_BY_DEFAULT', + didIndyNamespace: 'pool:localtest', + }, + resolutionMetadata: {}, + }) - expect(revocationRegistryDefintion).toMatchObject({ - revocationRegistryDefinitionId: `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag`, + const didIndyRevocationRegistryDefinition = await indyVdrAnonCredsRegistry.getRevocationRegistryDefinition( + agent.context, + didIndyRevocationRegistryId + ) + expect(didIndyRevocationRegistryDefinition).toMatchObject({ + revocationRegistryDefinitionId: didIndyRevocationRegistryId, revocationRegistryDefinition: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, revocDefType: 'CL_ACCUM', value: { maxCredNum: 100, @@ -313,7 +298,7 @@ describe('IndyVdrAnonCredsRegistry', () => { }, }, tag: 'tag', - credDefId: `TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG`, + credDefId: didIndyCredentialDefinitionId, }, revocationRegistryDefinitionMetadata: { issuanceType: 'ISSUANCE_BY_DEFAULT', @@ -322,26 +307,52 @@ describe('IndyVdrAnonCredsRegistry', () => { resolutionMetadata: {}, }) - const revocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( + const legacyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( + agent.context, + legacyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime + ) + + expect(legacyRevocationStatusList).toMatchObject({ + resolutionMetadata: {}, + revocationStatusList: { + issuerId: legacyIssuerId, + currentAccumulator: '1', + revRegId: legacyRevocationRegistryId, + revocationList: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + ], + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', + }, + }) + + const didIndyRevocationStatusList = await indyVdrAnonCredsRegistry.getRevocationStatusList( agent.context, - revocationRegistryDefinitionId, - response.result.txnMetadata.txnTime + didIndyRevocationRegistryId, + entryResponse.result.txnMetadata.txnTime ) - expect(revocationStatusList).toMatchObject({ + expect(didIndyRevocationStatusList).toMatchObject({ resolutionMetadata: {}, revocationStatusList: { - issuerId: 'TL1EaPFCZ8Si5aUrqScBDt', + issuerId: didIndyIssuerId, currentAccumulator: '1', - revRegId: `TL1EaPFCZ8Si5aUrqScBDt:4:TL1EaPFCZ8Si5aUrqScBDt:3:CL:${schemaResponse.schemaMetadata.indyLedgerSeqNo}:TAG:CL_ACCUM:tag`, + revRegId: didIndyRevocationRegistryId, revocationList: [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], - timestamp: response.result.txnMetadata.txnTime, + timestamp: entryResponse.result.txnMetadata.txnTime, + }, + revocationStatusListMetadata: { + didIndyNamespace: 'pool:localtest', }, - revocationStatusListMetadata: { didIndyNamespace: 'pool:localtest' }, }) }) }) diff --git a/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts index ec6724d576..65eee36cf3 100644 --- a/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts +++ b/packages/indy-vdr/tests/indy-vdr-did-registrar.e2e.test.ts @@ -1,94 +1,81 @@ +import type { IndyVdrDidCreateOptions } from '../src/dids/IndyVdrIndyDidRegistrar' + import { Key, - InjectionSymbols, - CacheModuleConfig, - InMemoryLruCache, JsonTransformer, KeyType, - SigningProviderRegistry, TypedArrayEncoder, DidCommV1Service, DidCommV2Service, DidDocumentService, + Agent, + DidsModule, } from '@aries-framework/core' import { convertPublicKeyToX25519, generateKeyPairFromSeed } from '@stablelib/ed25519' -import { Subject } from 'rxjs' -import { InMemoryStorageService } from '../../../tests/InMemoryStorageService' -import { agentDependencies, getAgentConfig, getAgentContext } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' -import { IndySdkWallet } from '../../indy-sdk/src' +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrModule, IndyVdrSovDidResolver } from '../src' import { IndyVdrIndyDidRegistrar } from '../src/dids/IndyVdrIndyDidRegistrar' import { IndyVdrIndyDidResolver } from '../src/dids/IndyVdrIndyDidResolver' import { indyDidFromNamespaceAndInitialKey } from '../src/dids/didIndyUtil' -import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' import { DID_INDY_REGEX } from '../src/utils/did' import { indyVdrModuleConfig } from './helpers' -const logger = testLogger -const wallet = new IndySdkWallet(indySdk, logger, new SigningProviderRegistry([])) - -const agentConfig = getAgentConfig('IndyVdrIndyDidRegistrar E2E', { logger }) - -const cache = new InMemoryLruCache({ limit: 200 }) -const indyVdrIndyDidResolver = new IndyVdrIndyDidResolver() -const indyVdrIndyDidRegistrar = new IndyVdrIndyDidRegistrar() - -let signerKey: Key - -const agentContext = getAgentContext({ - wallet, - agentConfig, - registerInstances: [ - [InjectionSymbols.Stop$, new Subject()], - [InjectionSymbols.AgentDependencies, agentDependencies], - [InjectionSymbols.StorageService, new InMemoryStorageService()], - [IndyVdrPoolService, new IndyVdrPoolService(logger, indyVdrModuleConfig)], - [CacheModuleConfig, new CacheModuleConfig({ cache })], - ], -}) +const agent = new Agent( + getAgentOptions( + 'Indy VDR Indy DID Registrar', + {}, + { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) -const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) +describe('Indy VDR Indy Did Registrar', () => { + let submitterDid: string -describe('Indy VDR registrar E2E', () => { beforeAll(async () => { - await wallet.createAndOpen(agentConfig.walletConfig) - - signerKey = await wallet.createKey({ - privateKey: TypedArrayEncoder.fromString('000000000000000000000000Trustee9'), - keyType: KeyType.Ed25519, - }) + await agent.initialize() + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString('000000000000000000000000Trustee9') + ) + submitterDid = `did:indy:pool:localtest:${unqualifiedSubmitterDid}` }) afterAll(async () => { - for (const pool of indyVdrPoolService.pools) { - pool.close() - } - - await wallet.delete() + await agent.shutdown() + await agent.wallet.delete() }) test('can register a did:indy without services', async () => { - const didRegistrationResult = await indyVdrIndyDidRegistrar.create(agentContext, { + const didRegistrationResult = await agent.dids.create({ method: 'indy', options: { - submitterDid: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', - submitterVerkey: signerKey.publicKeyBase58, + submitterDid, }, }) expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ didDocumentMetadata: {}, - didRegistrationMetadata: { - didIndyNamespace: 'pool:localtest', - }, + didRegistrationMetadata: {}, didState: { state: 'finished', did: expect.stringMatching(DID_INDY_REGEX), didDocument: { - '@context': ['https://w3id.org/did/v1'], + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: expect.stringMatching(DID_INDY_REGEX), alsoKnownAs: undefined, controller: undefined, @@ -109,14 +96,12 @@ describe('Indy VDR registrar E2E', () => { }) const did = didRegistrationResult.didState.did - if (!did) { - throw Error('did not defined') - } + if (!did) throw Error('did not defined') - const didResolutionResult = await indyVdrIndyDidResolver.resolve(agentContext, did) + const didResolutionResult = await agent.dids.resolve(did) expect(JsonTransformer.toJSON(didResolutionResult)).toMatchObject({ didDocument: { - '@context': ['https://w3id.org/did/v1'], + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: did, alsoKnownAs: undefined, controller: undefined, @@ -154,26 +139,22 @@ describe('Indy VDR registrar E2E', () => { 'pool:localtest', Key.fromPublicKey(keyPair.publicKey, KeyType.Ed25519) ) - const didRegistrationResult = await indyVdrIndyDidRegistrar.create(agentContext, { - method: 'indy', + const didRegistrationResult = await agent.dids.create({ did, options: { - submitterDid: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', - submitterVerkey: signerKey.publicKeyBase58, + submitterDid, verkey, }, }) expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ didDocumentMetadata: {}, - didRegistrationMetadata: { - didIndyNamespace: 'pool:localtest', - }, + didRegistrationMetadata: {}, didState: { state: 'finished', did, didDocument: { - '@context': ['https://w3id.org/did/v1'], + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: did, alsoKnownAs: undefined, controller: undefined, @@ -193,10 +174,10 @@ describe('Indy VDR registrar E2E', () => { }, }) - const didResult = await indyVdrIndyDidResolver.resolve(agentContext, did) + const didResult = await agent.dids.resolve(did) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ didDocument: { - '@context': ['https://w3id.org/did/v1'], + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], id: did, alsoKnownAs: undefined, controller: undefined, @@ -229,7 +210,7 @@ describe('Indy VDR registrar E2E', () => { .slice(0, 32) ) - const key = await wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) + const key = await agent.wallet.createKey({ privateKey, keyType: KeyType.Ed25519 }) const x25519PublicKeyBase58 = TypedArrayEncoder.toBase58(convertPublicKeyToX25519(key.publicKey)) const ed25519PublicKeyBase58 = TypedArrayEncoder.toBase58(key.publicKey) @@ -238,12 +219,10 @@ describe('Indy VDR registrar E2E', () => { Key.fromPublicKey(key.publicKey, KeyType.Ed25519) ) - const didRegistrationResult = await indyVdrIndyDidRegistrar.create(agentContext, { - method: 'indy', + const didRegistrationResult = await agent.dids.create({ did, options: { - submitterDid: 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt', - submitterVerkey: signerKey.publicKeyBase58, + submitterDid, useEndpointAttrib: true, verkey, services: [ @@ -271,7 +250,12 @@ describe('Indy VDR registrar E2E', () => { }) const expectedDidDocument = { - '@context': ['https://w3id.org/did/v1', 'https://didcomm.org/messaging/contexts/v2'], + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], id: did, alsoKnownAs: undefined, controller: undefined, @@ -319,9 +303,7 @@ describe('Indy VDR registrar E2E', () => { expect(JsonTransformer.toJSON(didRegistrationResult)).toMatchObject({ didDocumentMetadata: {}, - didRegistrationMetadata: { - didIndyNamespace: 'pool:localtest', - }, + didRegistrationMetadata: {}, didState: { state: 'finished', did, @@ -329,7 +311,7 @@ describe('Indy VDR registrar E2E', () => { }, }) - const didResult = await indyVdrIndyDidResolver.resolve(agentContext, did) + const didResult = await agent.dids.resolve(did) expect(JsonTransformer.toJSON(didResult)).toMatchObject({ didDocument: expectedDidDocument, didDocumentMetadata: {}, diff --git a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts deleted file mode 100644 index bc0e5f4ea8..0000000000 --- a/packages/indy-vdr/tests/indy-vdr-did-resolver.e2e.test.ts +++ /dev/null @@ -1,181 +0,0 @@ -import type { Key } from '@aries-framework/core' - -import { - TypedArrayEncoder, - CacheModuleConfig, - InMemoryLruCache, - JsonTransformer, - KeyType, - SigningProviderRegistry, -} from '@aries-framework/core' - -import { parseDid } from '../../core/src/modules/dids/domain/parse' -import { getAgentConfig, getAgentContext } from '../../core/tests/helpers' -import testLogger from '../../core/tests/logger' -import { IndySdkWallet } from '../../indy-sdk/src' -import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' -import { IndyVdrSovDidResolver } from '../src/dids' -import { IndyVdrPoolService } from '../src/pool/IndyVdrPoolService' -import { indyDidFromPublicKeyBase58 } from '../src/utils/did' - -import { createDidOnLedger, indyVdrModuleConfig } from './helpers' - -const logger = testLogger -const wallet = new IndySdkWallet(indySdk, logger, new SigningProviderRegistry([])) -const agentConfig = getAgentConfig('IndyVdrResolver E2E', { logger }) - -const cache = new InMemoryLruCache({ limit: 200 }) -const indyVdrSovDidResolver = new IndyVdrSovDidResolver() - -let signerKey: Key - -const agentContext = getAgentContext({ - wallet, - agentConfig, - registerInstances: [ - [IndyVdrPoolService, new IndyVdrPoolService(logger, indyVdrModuleConfig)], - [CacheModuleConfig, new CacheModuleConfig({ cache })], - ], -}) - -const indyVdrPoolService = agentContext.dependencyManager.resolve(IndyVdrPoolService) - -describe('indy-vdr DID Resolver E2E', () => { - beforeAll(async () => { - await wallet.createAndOpen(agentConfig.walletConfig) - - signerKey = await wallet.createKey({ - privateKey: TypedArrayEncoder.fromString('000000000000000000000000Trustee9'), - keyType: KeyType.Ed25519, - }) - }) - - afterAll(async () => { - for (const pool of indyVdrPoolService.pools) { - pool.close() - } - - await wallet.delete() - }) - - describe('did:sov resolver', () => { - test('can resolve a did sov using the pool', async () => { - const did = 'did:sov:TL1EaPFCZ8Si5aUrqScBDt' - const didResult = await indyVdrSovDidResolver.resolve(agentContext, did, parseDid(did)) - expect(JsonTransformer.toJSON(didResult)).toMatchObject({ - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - ], - id: did, - alsoKnownAs: undefined, - controller: undefined, - verificationMethod: [ - { - type: 'Ed25519VerificationKey2018', - controller: did, - id: `${did}#key-1`, - publicKeyBase58: expect.any(String), - }, - { - controller: did, - type: 'X25519KeyAgreementKey2019', - id: `${did}#key-agreement-1`, - publicKeyBase58: expect.any(String), - }, - ], - capabilityDelegation: undefined, - capabilityInvocation: undefined, - authentication: [`${did}#key-1`], - assertionMethod: [`${did}#key-1`], - keyAgreement: [`${did}#key-agreement-1`], - service: undefined, - }, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) - }) - - test('resolve a did with endpoints', async () => { - // First we need to create a new DID and add ATTRIB endpoint to it - const { did } = await createDidOnLedger( - indyVdrPoolService, - agentContext, - indyDidFromPublicKeyBase58(signerKey.publicKeyBase58), - signerKey - ) - - // DID created. Now resolve it - - const fullyQualifiedDid = `did:sov:${did}` - const didResult = await indyVdrSovDidResolver.resolve( - agentContext, - fullyQualifiedDid, - parseDid(fullyQualifiedDid) - ) - expect(JsonTransformer.toJSON(didResult)).toMatchObject({ - didDocument: { - '@context': [ - 'https://w3id.org/did/v1', - 'https://w3id.org/security/suites/ed25519-2018/v1', - 'https://w3id.org/security/suites/x25519-2019/v1', - 'https://didcomm.org/messaging/contexts/v2', - ], - id: fullyQualifiedDid, - alsoKnownAs: undefined, - controller: undefined, - verificationMethod: [ - { - type: 'Ed25519VerificationKey2018', - controller: fullyQualifiedDid, - id: `${fullyQualifiedDid}#key-1`, - publicKeyBase58: expect.any(String), - }, - { - controller: fullyQualifiedDid, - type: 'X25519KeyAgreementKey2019', - id: `${fullyQualifiedDid}#key-agreement-1`, - publicKeyBase58: expect.any(String), - }, - ], - capabilityDelegation: undefined, - capabilityInvocation: undefined, - authentication: [`${fullyQualifiedDid}#key-1`], - assertionMethod: [`${fullyQualifiedDid}#key-1`], - keyAgreement: [`${fullyQualifiedDid}#key-agreement-1`], - service: [ - { - id: `${fullyQualifiedDid}#endpoint`, - type: 'endpoint', - serviceEndpoint: 'https://agent.com', - }, - { - id: `${fullyQualifiedDid}#did-communication`, - type: 'did-communication', - priority: 0, - recipientKeys: [`${fullyQualifiedDid}#key-agreement-1`], - routingKeys: ['routingKey1', 'routingKey2'], - accept: ['didcomm/aip2;env=rfc19'], - serviceEndpoint: 'https://agent.com', - }, - { - id: `${fullyQualifiedDid}#didcomm-1`, - type: 'DIDComm', - serviceEndpoint: 'https://agent.com', - accept: ['didcomm/v2'], - routingKeys: ['routingKey1', 'routingKey2'], - }, - ], - }, - didDocumentMetadata: {}, - didResolutionMetadata: { - contentType: 'application/did+ld+json', - }, - }) - }) - }) -}) diff --git a/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..209b05e698 --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-indy-did-resolver.e2e.test.ts @@ -0,0 +1,143 @@ +import { DidsModule, Agent, TypedArrayEncoder, JsonTransformer } from '@aries-framework/core' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrModule } from '../src' +import { IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrSovDidResolver } from '../src/dids' + +import { createDidOnLedger, indyVdrModuleConfig } from './helpers' + +const agent = new Agent( + getAgentOptions( + 'Indy VDR Indy DID resolver', + {}, + { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) + +describe('indy-vdr DID Resolver E2E', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + test('resolve a did:indy did', async () => { + const did = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt' + const didResult = await agent.dids.resolve(did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': ['https://w3id.org/did/v1', 'https://w3id.org/security/suites/ed25519-2018/v1'], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + assertionMethod: undefined, + keyAgreement: undefined, + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('resolve a did with endpoints', async () => { + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString('000000000000000000000000Trustee9') + ) + + // First we need to create a new DID and add ATTRIB endpoint to it + const { did } = await createDidOnLedger(agent, `did:indy:pool:localtest:${unqualifiedSubmitterDid}`) + + // DID created. Now resolve it + const didResult = await agent.dids.resolve(did) + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#verkey`, + publicKeyBase58: expect.any(String), + }, + { + controller: did, + type: 'X25519KeyAgreementKey2019', + id: `${did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#verkey`], + assertionMethod: undefined, + keyAgreement: [`${did}#key-agreement-1`], + service: [ + { + id: `${did}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${did}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${did}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + { + id: `${did}#didcomm-1`, + accept: ['didcomm/v2'], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'DIDComm', + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts b/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts new file mode 100644 index 0000000000..2ff501c641 --- /dev/null +++ b/packages/indy-vdr/tests/indy-vdr-sov-did-resolver.e2e.test.ts @@ -0,0 +1,157 @@ +import { DidsModule, Agent, TypedArrayEncoder, JsonTransformer } from '@aries-framework/core' + +import { getAgentOptions, importExistingIndyDidFromPrivateKey } from '../../core/tests/helpers' +import { IndySdkModule } from '../../indy-sdk/src' +import { indySdk } from '../../indy-sdk/tests/setupIndySdkModule' +import { IndyVdrModule } from '../src' +import { IndyVdrIndyDidRegistrar, IndyVdrIndyDidResolver, IndyVdrSovDidResolver } from '../src/dids' +import { parseIndyDid } from '../src/dids/didIndyUtil' + +import { createDidOnLedger, indyVdrModuleConfig } from './helpers' + +const agent = new Agent( + getAgentOptions( + 'Indy VDR Sov DID resolver', + {}, + { + indyVdr: new IndyVdrModule({ + networks: indyVdrModuleConfig.networks, + }), + indySdk: new IndySdkModule({ + indySdk, + }), + dids: new DidsModule({ + registrars: [new IndyVdrIndyDidRegistrar()], + resolvers: [new IndyVdrIndyDidResolver(), new IndyVdrSovDidResolver()], + }), + } + ) +) + +describe('Indy VDR Sov DID Resolver', () => { + beforeAll(async () => { + await agent.initialize() + }) + + afterAll(async () => { + await agent.shutdown() + await agent.wallet.delete() + }) + + test('resolve a did:sov did', async () => { + const did = 'did:sov:TL1EaPFCZ8Si5aUrqScBDt' + const didResult = await agent.dids.resolve(did) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + ], + id: did, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: did, + id: `${did}#key-1`, + publicKeyBase58: expect.any(String), + }, + { + controller: did, + type: 'X25519KeyAgreementKey2019', + id: `${did}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${did}#key-1`], + assertionMethod: [`${did}#key-1`], + keyAgreement: [`${did}#key-agreement-1`], + service: undefined, + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) + + test('resolve a did with endpoints', async () => { + const unqualifiedSubmitterDid = await importExistingIndyDidFromPrivateKey( + agent, + TypedArrayEncoder.fromString('000000000000000000000000Trustee9') + ) + + // First we need to create a new DID and add ATTRIB endpoint to it + const { did } = await createDidOnLedger(agent, `did:indy:pool:localtest:${unqualifiedSubmitterDid}`) + const { namespaceIdentifier } = parseIndyDid(did) + const sovDid = `did:sov:${namespaceIdentifier}` + + // DID created. Now resolve it + const didResult = await agent.dids.resolve(sovDid) + + expect(JsonTransformer.toJSON(didResult)).toMatchObject({ + didDocument: { + '@context': [ + 'https://w3id.org/did/v1', + 'https://w3id.org/security/suites/ed25519-2018/v1', + 'https://w3id.org/security/suites/x25519-2019/v1', + 'https://didcomm.org/messaging/contexts/v2', + ], + id: sovDid, + alsoKnownAs: undefined, + controller: undefined, + verificationMethod: [ + { + type: 'Ed25519VerificationKey2018', + controller: sovDid, + id: `${sovDid}#key-1`, + publicKeyBase58: expect.any(String), + }, + { + controller: sovDid, + type: 'X25519KeyAgreementKey2019', + id: `${sovDid}#key-agreement-1`, + publicKeyBase58: expect.any(String), + }, + ], + capabilityDelegation: undefined, + capabilityInvocation: undefined, + authentication: [`${sovDid}#key-1`], + assertionMethod: [`${sovDid}#key-1`], + keyAgreement: [`${sovDid}#key-agreement-1`], + service: [ + { + id: `${sovDid}#endpoint`, + serviceEndpoint: 'http://localhost:3000', + type: 'endpoint', + }, + { + id: `${sovDid}#did-communication`, + accept: ['didcomm/aip2;env=rfc19'], + priority: 0, + recipientKeys: [`${sovDid}#key-agreement-1`], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'did-communication', + }, + { + id: `${sovDid}#didcomm-1`, + accept: ['didcomm/v2'], + routingKeys: ['a-routing-key'], + serviceEndpoint: 'http://localhost:3000', + type: 'DIDComm', + }, + ], + }, + didDocumentMetadata: {}, + didResolutionMetadata: { + contentType: 'application/did+ld+json', + }, + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index a96c0c26f9..ae76e3b622 100644 --- a/yarn.lock +++ b/yarn.lock @@ -896,22 +896,22 @@ dependencies: fast-text-encoding "^1.0.3" -"@hyperledger/indy-vdr-nodejs@^0.1.0-dev.6": - version "0.1.0-dev.6" - resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.1.0-dev.6.tgz#28946107feb6c641839de843cc7ddca9eef01f8d" - integrity sha512-jtFRkfjiveKIfeyTx9qDAUXLtpFaiZlUGN2ts2zX8QvY6XGZEKc6LvhMPzLW5kLW34u6ldEqjG4mjyAawUKRsA== +"@hyperledger/indy-vdr-nodejs@^0.1.0-dev.10": + version "0.1.0-dev.10" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-nodejs/-/indy-vdr-nodejs-0.1.0-dev.10.tgz#f5fc988b3c11766475f7f084f814af8a4eca0caf" + integrity sha512-wETVax9HRidhRTRiRqwLTj/looNCvjZ3vdqMoqmPbmet5sM9eRkU9v4ipz4paqz4LiZipTx2fisLyYskKW9g6A== dependencies: - "@hyperledger/indy-vdr-shared" "0.1.0-dev.6" + "@hyperledger/indy-vdr-shared" "0.1.0-dev.10" "@mapbox/node-pre-gyp" "^1.0.10" ffi-napi "^4.0.3" ref-array-di "^1.2.2" ref-napi "^3.0.3" ref-struct-di "^1.1.1" -"@hyperledger/indy-vdr-shared@0.1.0-dev.6", "@hyperledger/indy-vdr-shared@^0.1.0-dev.6": - version "0.1.0-dev.6" - resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.1.0-dev.6.tgz#345b6ff60d29d74615ee882e225af674315a0bda" - integrity sha512-MIUdm3zIwKfFZmZSwbAPiZHEZE0HhsIPjeEWiu//Z1m9GDDSNhEyxsHuVN17pE0pcxwbuCQrIaK3v4Tc6x0jJw== +"@hyperledger/indy-vdr-shared@0.1.0-dev.10", "@hyperledger/indy-vdr-shared@^0.1.0-dev.10": + version "0.1.0-dev.10" + resolved "https://registry.yarnpkg.com/@hyperledger/indy-vdr-shared/-/indy-vdr-shared-0.1.0-dev.10.tgz#2a6d09a3a6ae3458ae9209ab7b6f09435e88b9c9" + integrity sha512-7fr9/Iliu0u94qE8u1g/RoLJOwMH0kFdclttwBHfTAKTQyfoXUPe1yIZ8ITRgLwnmFYPQpRa54lU+uv15c37fg== "@istanbuljs/load-nyc-config@^1.0.0": version "1.1.0"