Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(indy-vdr)!: extend did:indy support #1362

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions packages/anoncreds/tests/legacyAnonCredsSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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({
Expand Down
2 changes: 1 addition & 1 deletion packages/core/tests/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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).
*/
Expand Down Expand Up @@ -334,7 +331,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(
Expand Down
5 changes: 5 additions & 0 deletions packages/indy-sdk/src/anoncreds/utils/identifiers.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
/**
* 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 =
Expand Down
20 changes: 15 additions & 5 deletions packages/indy-sdk/src/dids/IndySdkIndyDidRegistrar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar {
}
}

public async registerPublicDid(
private async registerPublicDid(
agentContext: AgentContext,
pool: IndySdkPool,
unqualifiedSubmitterDid: string,
Expand Down Expand Up @@ -249,7 +249,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar {
}
}

public async setEndpointsForDid(
private async setEndpointsForDid(
agentContext: AgentContext,
pool: IndySdkPool,
unqualifiedDid: string,
Expand Down Expand Up @@ -296,9 +296,7 @@ export class IndySdkIndyDidRegistrar implements DidRegistrar {
}
}

export interface IndySdkIndyDidCreateOptions extends DidCreateOptions {
method: 'indy'
did?: string
interface IndySdkIndyDidCreateOptionsBase extends DidCreateOptions {
// The indy sdk can only publish a very limited did document (what is mostly known as a legacy did:sov did) and thus we require everything
// needed to construct the did document to be passed through the options object.
didDocument?: never
Expand All @@ -313,3 +311,15 @@ export interface IndySdkIndyDidCreateOptions extends DidCreateOptions {
privateKey?: Buffer
}
}

interface IndySdkIndyDidCreateOptionsWithDid extends IndySdkIndyDidCreateOptionsBase {
method?: never
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very interesting approach to prevent some frustrating errors at runtime. I think it can be extended to general DidsApi DidCreateOptions.

Or maybe method can be omitted in DidRegistrars XXXDidCreateOptions (as it is redundant if you are calling directly to the registrar).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah removing it makes sense. I'd like to do that in a separate PR if that's okay as it spans all did registrars

did: string
}

interface IndySdkIndyDidCreateOptionsWithoutDid extends IndySdkIndyDidCreateOptionsBase {
method: 'indy'
did?: never
}

export type IndySdkIndyDidCreateOptions = IndySdkIndyDidCreateOptionsWithDid | IndySdkIndyDidCreateOptionsWithoutDid
25 changes: 16 additions & 9 deletions packages/indy-sdk/src/dids/IndySdkSovDidResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down Expand Up @@ -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<IndySdk>(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,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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',
Expand All @@ -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<undefined, undefined>(indySdkIndyDidRegistrar, 'registerPublicDid')
registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve())

const result = await indySdkIndyDidRegistrar.create(agentContext, {
Expand Down Expand Up @@ -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<undefined, undefined>(indySdkIndyDidRegistrar, 'registerPublicDid')
registerPublicDidSpy.mockImplementationOnce(() => Promise.resolve())

const result = await indySdkIndyDidRegistrar.create(agentContext, {
method: 'indy',
did: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ',
options: {
verkey: 'E6D1m3eERqCueX4ZgMCY14B4NceAr6XP2HyVqt55gDhu',
Expand Down Expand Up @@ -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<undefined, undefined>(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<undefined, undefined>(indySdkIndyDidRegistrar, 'setEndpointsForDid')
setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined))

const result = await indySdkIndyDidRegistrar.create(agentContext, {
Expand Down Expand Up @@ -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<undefined, undefined>(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<undefined, undefined>(indySdkIndyDidRegistrar, 'setEndpointsForDid')
setEndpointsForDidSpy.mockImplementationOnce(() => Promise.resolve(undefined))

const saveCalled = jest.fn()
Expand Down
6 changes: 2 additions & 4 deletions packages/indy-sdk/tests/indy-did-registrar.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<ReturnType<typeof getIndySdkModules>>

describe('Indy SDK Indy Did Registrar', () => {
beforeAll(async () => {
agent = new Agent(agentOptions)
await agent.initialize()
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -136,7 +135,7 @@ describe('IndySdkAnonCredsRegistry', () => {
tag: 'TAG',
schemaId: didIndySchemaId,
type: 'CL',
value: {},
value: credentialDefinitionValue,
},
credentialDefinitionId: didIndyCredentialDefinitionId,
state: 'finished',
Expand Down Expand Up @@ -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`
Expand Down
2 changes: 1 addition & 1 deletion packages/indy-sdk/tests/sov-did-resolver.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading