Skip to content

Commit

Permalink
feat(ledger): smart schema and credential definition registration (#900)
Browse files Browse the repository at this point in the history
Signed-off-by: Moriarty <moritz@animo.id>
  • Loading branch information
morrieinmaas authored and TimoGlastra committed Aug 26, 2022
1 parent adfa65b commit 1e708e9
Show file tree
Hide file tree
Showing 8 changed files with 648 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { CredDef } from 'indy-sdk'

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

export interface AnonCredsCredentialDefinitionRecordProps {
credentialDefinition: CredDef
}

export type DefaultAnonCredsCredentialDefinitionTags = {
credentialDefinitionId: string
issuerDid: string
schemaId: string
tag: string
}

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

public constructor(props: AnonCredsCredentialDefinitionRecordProps) {
super()

if (props) {
this.credentialDefinition = props.credentialDefinition
}
}

public getTags() {
return {
...this._tags,
credentialDefinitionId: this.credentialDefinition.id,
issuerDid: didFromCredentialDefinitionId(this.credentialDefinition.id),
schemaId: this.credentialDefinition.schemaId,
tag: this.credentialDefinition.tag,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { AgentContext } from '../../../agent/context/AgentContext'

import { EventEmitter } from '../../../agent/EventEmitter'
import { InjectionSymbols } from '../../../constants'
import { injectable, inject } from '../../../plugins'
import { Repository } from '../../../storage/Repository'
import { StorageService } from '../../../storage/StorageService'

import { AnonCredsCredentialDefinitionRecord } from './AnonCredsCredentialDefinitionRecord'

@injectable()
export class AnonCredsCredentialDefinitionRepository extends Repository<AnonCredsCredentialDefinitionRecord> {
public constructor(
@inject(InjectionSymbols.StorageService) storageService: StorageService<AnonCredsCredentialDefinitionRecord>,
eventEmitter: EventEmitter
) {
super(AnonCredsCredentialDefinitionRecord, storageService, eventEmitter)
}

public async getByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) {
return this.getSingleByQuery(agentContext, { credentialDefinitionId })
}

public async findByCredentialDefinitionId(agentContext: AgentContext, credentialDefinitionId: string) {
return this.findSingleByQuery(agentContext, { credentialDefinitionId })
}
}
39 changes: 39 additions & 0 deletions packages/core/src/modules/indy/repository/AnonCredsSchemaRecord.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Schema } from 'indy-sdk'

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

export interface AnonCredsSchemaRecordProps {
schema: Schema
}

export type DefaultAnonCredsSchemaTags = {
schemaId: string
schemaIssuerDid: string
schemaName: string
schemaVersion: string
}

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.schema = props.schema
}
}

public getTags() {
return {
...this._tags,
schemaId: this.schema.id,
schemaIssuerDid: didFromSchemaId(this.schema.id),
schemaName: this.schema.name,
schemaVersion: this.schema.version,
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { AgentContext } from '../../../agent/context/AgentContext'

import { EventEmitter } from '../../../agent/EventEmitter'
import { InjectionSymbols } from '../../../constants'
import { injectable, inject } from '../../../plugins'
import { Repository } from '../../../storage/Repository'
import { StorageService } from '../../../storage/StorageService'

import { AnonCredsSchemaRecord } from './AnonCredsSchemaRecord'

@injectable()
export class AnonCredsSchemaRepository extends Repository<AnonCredsSchemaRecord> {
public constructor(
@inject(InjectionSymbols.StorageService) storageService: StorageService<AnonCredsSchemaRecord>,
eventEmitter: EventEmitter
) {
super(AnonCredsSchemaRecord, storageService, eventEmitter)
}

public async getBySchemaId(agentContext: AgentContext, schemaId: string) {
return this.getSingleByQuery(agentContext, { schemaId: schemaId })
}

public async findBySchemaId(agentContext: AgentContext, schemaId: string) {
return await this.findSingleByQuery(agentContext, { schemaId: schemaId })
}
}
84 changes: 75 additions & 9 deletions packages/core/src/modules/ledger/LedgerModule.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
import type { DependencyManager } from '../../plugins'
import type { IndyPoolConfig } from './IndyPool'
import type { CredentialDefinitionTemplate, SchemaTemplate } from './services'
import type { NymRole } from 'indy-sdk'
import type { SchemaTemplate, CredentialDefinitionTemplate } from './services'
import type { CredDef, NymRole, Schema } from 'indy-sdk'

import { AgentContext } from '../../agent'
import { AriesFrameworkError } from '../../error'
import { IndySdkError } from '../../error/IndySdkError'
import { injectable, module } from '../../plugins'
import { isIndyError } from '../../utils/indyError'
import { AnonCredsCredentialDefinitionRepository } from '../indy/repository/AnonCredsCredentialDefinitionRepository'
import { AnonCredsSchemaRepository } from '../indy/repository/AnonCredsSchemaRepository'

import { IndyLedgerService, IndyPoolService } from './services'
import { generateCredentialDefinitionId, generateSchemaId } from './ledgerUtil'
import { IndyPoolService, IndyLedgerService } from './services'

@module()
@injectable()
export class LedgerModule {
private ledgerService: IndyLedgerService
private agentContext: AgentContext

public constructor(ledgerService: IndyLedgerService, agentContext: AgentContext) {
private anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository
private anonCredsSchemaRepository: AnonCredsSchemaRepository

public constructor(
ledgerService: IndyLedgerService,
agentContext: AgentContext,
anonCredsCredentialDefinitionRepository: AnonCredsCredentialDefinitionRepository,
anonCredsSchemaRepository: AnonCredsSchemaRepository
) {
this.ledgerService = ledgerService
this.agentContext = agentContext
this.anonCredsCredentialDefinitionRepository = anonCredsCredentialDefinitionRepository
this.anonCredsSchemaRepository = anonCredsSchemaRepository
}

public setPools(poolConfigs: IndyPoolConfig[]) {
Expand Down Expand Up @@ -45,18 +59,47 @@ export class LedgerModule {
return this.ledgerService.getPublicDid(this.agentContext, did)
}

public async registerSchema(schema: SchemaTemplate) {
public async getSchema(id: string) {
return this.ledgerService.getSchema(this.agentContext, id)
}

public async registerSchema(schema: SchemaTemplate): Promise<Schema> {
const did = this.agentContext.wallet.publicDid?.did

if (!did) {
throw new AriesFrameworkError('Agent has no public DID.')
}

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

// 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 schemaFromLedger = await this.findBySchemaIdOnLedger(schemaId)
if (schemaFromLedger) return schemaFromLedger
return this.ledgerService.registerSchema(this.agentContext, did, schema)
}

public async getSchema(id: string) {
return this.ledgerService.getSchema(this.agentContext, id)
private async findBySchemaIdOnLedger(schemaId: string) {
try {
return await this.ledgerService.getSchema(this.agentContext, schemaId)
} catch (e) {
if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null

throw e
}
}

private async findByCredentialDefinitionIdOnLedger(credentialDefinitionId: string): Promise<CredDef | null> {
try {
return await this.ledgerService.getCredentialDefinition(this.agentContext, credentialDefinitionId)
} catch (e) {
if (e instanceof IndySdkError && isIndyError(e.cause, 'LedgerNotFound')) return null

throw e
}
}

public async registerCredentialDefinition(
Expand All @@ -68,7 +111,30 @@ export class LedgerModule {
throw new AriesFrameworkError('Agent has no public DID.')
}

return this.ledgerService.registerCredentialDefinition(this.agentContext, did, {
// Construct credential definition ID
const credentialDefinitionId = generateCredentialDefinitionId(
did,
credentialDefinitionTemplate.schema.seqNo,
credentialDefinitionTemplate.tag
)

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

// Check for the credential on the ledger.
const credentialDefinitionOnLedger = await this.findByCredentialDefinitionIdOnLedger(credentialDefinitionId)
if (credentialDefinitionOnLedger) {
throw new AriesFrameworkError(
`No credential definition record found and credential definition ${credentialDefinitionId} already exists on the ledger.`
)
}

// Register the credential
return await this.ledgerService.registerCredentialDefinition(this.agentContext, did, {
...credentialDefinitionTemplate,
signatureType: 'CL',
})
Expand Down
Loading

0 comments on commit 1e708e9

Please sign in to comment.