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: add indynamespace for ledger id for anoncreds #965

Merged
Merged
Show file tree
Hide file tree
Changes from 17 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
1 change: 1 addition & 0 deletions demo/src/BaseAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export class BaseAgent {
{
genesisTransactions: bcovrin,
id: 'greenlights' + name,
indyNamespace: 'greenlights' + name,
isProduction: false,
},
],
Expand Down
2 changes: 2 additions & 0 deletions docs/getting-started/ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,13 @@ const agentConfig: InitConfig = {
indyLedgers: [
{
id: 'sovrin-main',
didIndyNamespace: 'sovrin',
isProduction: true,
genesisPath: './genesis/sovrin-main.txn',
},
{
id: 'bcovrin-test',
didIndyNamespace: 'bcovrin:test',
isProduction: false,
genesisTransactions: 'XXXX',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ export class IndyCredentialFormatService extends CredentialFormatService<IndyCre
credentialDefinition,
}
)

TimoGlastra marked this conversation as resolved.
Show resolved Hide resolved
credentialRecord.metadata.set(CredentialMetadataKeys.IndyRequest, credentialRequestMetadata)
credentialRecord.metadata.set(CredentialMetadataKeys.IndyCredential, {
credentialDefinitionId: credentialOffer.cred_def_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const { config, agentDependencies } = getBaseConfig('Faber Dids Registrar', {
id: `localhost`,
isProduction: false,
genesisPath,
indyNamespace: 'localhost',
transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' },
},
],
Expand Down Expand Up @@ -200,7 +201,7 @@ describe('dids', () => {
qualifiedIndyDid: `did:indy:localhost:${indyDid}`,
},
didRegistrationMetadata: {
indyNamespace: 'localhost',
didIndyNamespace: 'localhost',
},
didState: {
state: 'finished',
Expand Down
7 changes: 3 additions & 4 deletions packages/core/src/modules/dids/methods/sov/SovDidRegistrar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,8 @@ export class SovDidRegistrar implements DidRegistrar {
// Build did document.
const didDocument = didDocumentBuilder.build()

// FIXME: we need to update this to the `indyNamespace` once https://github.com/hyperledger/aries-framework-javascript/issues/944 has been resolved
const indyNamespace = this.indyPoolService.ledgerWritePool.config.id
const qualifiedIndyDid = `did:indy:${indyNamespace}:${unqualifiedIndyDid}`
const didIndyNamespace = this.indyPoolService.ledgerWritePool.config.indyNamespace
const qualifiedIndyDid = `did:indy:${didIndyNamespace}:${unqualifiedIndyDid}`

// Save the did so we know we created it and can issue with it
const didRecord = new DidRecord({
Expand All @@ -122,7 +121,7 @@ export class SovDidRegistrar implements DidRegistrar {
qualifiedIndyDid,
},
didRegistrationMetadata: {
indyNamespace,
didIndyNamespace,
},
didState: {
state: 'finished',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const IndyLedgerServiceMock = IndyLedgerService as jest.Mock<IndyLedgerService>
jest.mock('../../../../ledger/services/IndyPoolService')
const IndyPoolServiceMock = IndyPoolService as jest.Mock<IndyPoolService>
const indyPoolServiceMock = new IndyPoolServiceMock()
mockProperty(indyPoolServiceMock, 'ledgerWritePool', { config: { id: 'pool1' } } as IndyPool)
mockProperty(indyPoolServiceMock, 'ledgerWritePool', { config: { id: 'pool1', indyNamespace: 'pool1' } } as IndyPool)

const agentConfig = getAgentConfig('SovDidRegistrar')

Expand Down Expand Up @@ -148,7 +148,7 @@ describe('DidRegistrar', () => {
qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ',
},
didRegistrationMetadata: {
indyNamespace: 'pool1',
didIndyNamespace: 'pool1',
},
didState: {
state: 'finished',
Expand Down Expand Up @@ -222,7 +222,7 @@ describe('DidRegistrar', () => {
qualifiedIndyDid: 'did:indy:pool1:R1xKJw17sUoXhejEpugMYJ',
},
didRegistrationMetadata: {
indyNamespace: 'pool1',
didIndyNamespace: 'pool1',
},
didState: {
state: 'finished',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,28 +1,23 @@
import type { CredDef } from 'indy-sdk'

import { BaseRecord } from '../../../storage/BaseRecord'
import { didFromCredentialDefinitionId } from '../../../utils/did'
import { uuid } from '../../../utils/uuid'

export interface AnonCredsCredentialDefinitionRecordProps {
credentialDefinition: CredDef
}

export type DefaultAnonCredsCredentialDefinitionTags = {
credentialDefinitionId: string
morrieinmaas marked this conversation as resolved.
Show resolved Hide resolved
issuerDid: string
schemaId: string
tag: string
}

export class AnonCredsCredentialDefinitionRecord extends BaseRecord<DefaultAnonCredsCredentialDefinitionTags> {
export class AnonCredsCredentialDefinitionRecord extends BaseRecord {
public static readonly type = 'AnonCredsCredentialDefinitionRecord'
public readonly type = AnonCredsCredentialDefinitionRecord.type

public readonly credentialDefinition!: CredDef

public constructor(props: AnonCredsCredentialDefinitionRecordProps) {
super()

if (props) {
this.id = uuid()
this.credentialDefinition = props.credentialDefinition
}
}
Expand All @@ -31,9 +26,6 @@ export class AnonCredsCredentialDefinitionRecord extends BaseRecord<DefaultAnonC
return {
...this._tags,
credentialDefinitionId: this.credentialDefinition.id,
morrieinmaas marked this conversation as resolved.
Show resolved Hide resolved
issuerDid: didFromCredentialDefinitionId(this.credentialDefinition.id),
schemaId: this.credentialDefinition.schemaId,
tag: this.credentialDefinition.tag,
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import type { Schema } from 'indy-sdk'

import { BaseRecord } from '../../../storage/BaseRecord'
import { didFromSchemaId } from '../../../utils/did'
import { uuid } from '../../../utils/uuid'

export interface AnonCredsSchemaRecordProps {
schema: Schema
id?: string
}

export type DefaultAnonCredsSchemaTags = {
Expand All @@ -17,12 +19,14 @@ export type DefaultAnonCredsSchemaTags = {
export class AnonCredsSchemaRecord extends BaseRecord<DefaultAnonCredsSchemaTags> {
public static readonly type = 'AnonCredsSchemaRecord'
public readonly type = AnonCredsSchemaRecord.type

public readonly schema!: Schema

public constructor(props: AnonCredsSchemaRecordProps) {
super()

if (props) {
this.id = props.id ?? uuid()
this.schema = props.schema
}
}
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/modules/ledger/IndyPool.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { AgentDependencies } from '../../agent/AgentDependencies'
import type { Logger } from '../../logger'
import type { FileSystem } from '../../storage/FileSystem'
import type { DidIndyNamespace } from '../../utils/indyIdentifiers'
import type * as Indy from 'indy-sdk'
import type { Subject } from 'rxjs'

Expand All @@ -20,6 +21,7 @@ export interface IndyPoolConfig {
genesisTransactions?: string
id: string
isProduction: boolean
indyNamespace: DidIndyNamespace
transactionAuthorAgreement?: TransactionAuthorAgreement
}

Expand Down Expand Up @@ -52,6 +54,10 @@ export class IndyPool {
})
}

public get didIndyNamespace(): string {
return this.didIndyNamespace
}

public get id() {
return this.poolConfig.id
}
Expand Down
63 changes: 55 additions & 8 deletions packages/core/src/modules/ledger/LedgerApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ import { AriesFrameworkError } from '../../error'
import { IndySdkError } from '../../error/IndySdkError'
import { injectable } from '../../plugins'
import { isIndyError } from '../../utils/indyError'
import {
getQualifiedIdentifierCredentialDefinition,
getQualifiedIdentifierSchema,
getLegacyIndyCredentialDefinitionId,
getLegacyIndySchemaId,
} from '../../utils/indyIdentifiers'
import { AnonCredsCredentialDefinitionRecord } from '../indy/repository/AnonCredsCredentialDefinitionRecord'
import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository'
import { AnonCredsSchemaRecord } from '../indy/repository/AnonCredsSchemaRecord'
import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository'

import { LedgerModuleConfig } from './LedgerModuleConfig'
Expand Down Expand Up @@ -75,14 +83,31 @@ export class LedgerApi {

const schemaId = generateSchemaId(did, schema.name, schema.version)

// Generate the qualified ID
const qualifiedIdentifier = getQualifiedIdentifierSchema(this.ledgerService.getDidIndyNamespace(), schema, schemaId)

// Try find the schema in the wallet
const schemaRecord = await this.anonCredsSchemaRepository.findBySchemaId(this.agentContext, schemaId)
// Schema in wallet
if (schemaRecord) return schemaRecord.schema
const schemaRecord = await this.anonCredsSchemaRepository.findById(this.agentContext, qualifiedIdentifier)
// Schema in wallet
if (schemaRecord) {
// Transform qualified to unqualified
schemaRecord.schema.id = getLegacyIndySchemaId(schemaRecord.schema.id)
TimoGlastra marked this conversation as resolved.
Show resolved Hide resolved
morrieinmaas marked this conversation as resolved.
Show resolved Hide resolved
return {
...schemaRecord?.schema,
TimoGlastra marked this conversation as resolved.
Show resolved Hide resolved
id: getLegacyIndySchemaId(schemaRecord.id),
}
}

const schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId)

if (schemaFromLedger) return schemaFromLedger
return this.ledgerService.registerSchema(this.agentContext, did, schema)
const createdSchema = await this.ledgerService.registerSchema(this.agentContext, did, schema)

const anonCredsSchema = new AnonCredsSchemaRecord({
schema: { ...createdSchema, id: qualifiedIdentifier },
})
await this.anonCredsSchemaRepository.save(this.agentContext, anonCredsSchema)
return createdSchema
}

private async findBySchemaIdOnLedger(schemaId: string) {
Expand Down Expand Up @@ -121,12 +146,27 @@ export class LedgerApi {
credentialDefinitionTemplate.tag
)

// Construct qualified identifier
const qualifiedIdentifier = getQualifiedIdentifierCredentialDefinition(
this.ledgerService.getDidIndyNamespace(),
credentialDefinitionId,
credentialDefinitionTemplate
)

// Check if the credential exists in wallet. If so, return it
const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findByCredentialDefinitionId(
const credentialDefinitionRecord = await this.anonCredsCredentialDefinitionRepository.findById(
this.agentContext,
credentialDefinitionId
qualifiedIdentifier
)
if (credentialDefinitionRecord) return credentialDefinitionRecord.credentialDefinition

// Credential Definition in wallet
if (credentialDefinitionRecord) {
// Transform qualified to unqualified
credentialDefinitionRecord.credentialDefinition.id = getLegacyIndyCredentialDefinitionId(
credentialDefinitionRecord.credentialDefinition.id
)
return credentialDefinitionRecord.credentialDefinition
TimoGlastra marked this conversation as resolved.
Show resolved Hide resolved
morrieinmaas marked this conversation as resolved.
Show resolved Hide resolved
}

// Check for the credential on the ledger.
const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId)
Expand All @@ -137,10 +177,17 @@ export class LedgerApi {
}

// Register the credential
return await this.ledgerService.registerCredentialDefinition(this.agentContext, did, {
const registeredDefinition = await this.ledgerService.registerCredentialDefinition(this.agentContext, did, {
...credentialDefinitionTemplate,
signatureType: 'CL',
})
// Replace the unqualified with qualified Identifier in anonCred
const anonCredCredential = new AnonCredsCredentialDefinitionRecord({
morrieinmaas marked this conversation as resolved.
Show resolved Hide resolved
credentialDefinition: { ...registeredDefinition, id: qualifiedIdentifier },
})
await this.anonCredsCredentialDefinitionRepository.save(this.agentContext, anonCredCredential)

return registeredDefinition
}

public async getCredentialDefinition(id: string) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ const CacheRepositoryMock = CacheRepository as jest.Mock<CacheRepository>

const pools: IndyPoolConfig[] = [
{
id: 'sovrinMain',
id: 'sovrin',
indyNamespace: 'sovrin',
isProduction: true,
genesisTransactions: 'xxx',
transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,30 +24,35 @@ const CacheRepositoryMock = CacheRepository as jest.Mock<CacheRepository>
const pools: IndyPoolConfig[] = [
{
id: 'sovrinMain',
indyNamespace: 'sovrin',
isProduction: true,
genesisTransactions: 'xxx',
transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' },
},
{
id: 'sovrinBuilder',
indyNamespace: 'sovrin:builder',
isProduction: false,
genesisTransactions: 'xxx',
transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' },
},
{
id: 'sovrinStaging',
id: 'sovringStaging',
indyNamespace: 'sovrin:staging',
isProduction: false,
genesisTransactions: 'xxx',
transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' },
},
{
id: 'indicioMain',
indyNamespace: 'indicio',
isProduction: true,
genesisTransactions: 'xxx',
transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' },
},
{
id: 'bcovrinTest',
indyNamespace: 'bcovrin:test',
isProduction: false,
genesisTransactions: 'xxx',
transactionAuthorAgreement: { version: '1', acceptanceMechanism: 'accept' },
Expand Down Expand Up @@ -280,6 +285,7 @@ describe('IndyPoolService', () => {
const { pool } = await poolService.getPoolForDid(agentContext, did)

expect(pool.config.id).toBe('sovrinBuilder')
expect(pool.config.indyNamespace).toBe('sovrin:builder')

const cacheRecord = spy.mock.calls[0][1]
expect(cacheRecord.entries.length).toBe(1)
Expand Down
Loading