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(anoncreds): use legacy prover did #1374

Merged
4 changes: 2 additions & 2 deletions packages/anoncreds-rs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
"dependencies": {
"@aries-framework/core": "0.3.3",
"@aries-framework/anoncreds": "0.3.3",
"@hyperledger/anoncreds-shared": "^0.1.0-dev.9",
"@hyperledger/anoncreds-shared": "^0.1.0-dev.10",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"rxjs": "^7.2.0",
"tsyringe": "^4.7.0"
},
"devDependencies": {
"@hyperledger/anoncreds-nodejs": "^0.1.0-dev.9",
"@hyperledger/anoncreds-nodejs": "^0.1.0-dev.10",
"rimraf": "^4.0.7",
"typescript": "~4.9.4"
}
Expand Down
14 changes: 11 additions & 3 deletions packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ import {
AnonCredsCredentialRecord,
AnonCredsLinkSecretRepository,
AnonCredsCredentialRepository,
legacyIndyCredentialDefinitionIdRegex,
} from '@aries-framework/anoncreds'
import { utils, injectable } from '@aries-framework/core'
import { TypedArrayEncoder, AriesFrameworkError, utils, injectable } from '@aries-framework/core'
import {
anoncreds,
Credential,
Expand Down Expand Up @@ -193,7 +194,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService {
agentContext: AgentContext,
options: CreateCredentialRequestOptions
): Promise<CreateCredentialRequestReturn> {
const { credentialDefinition, credentialOffer } = options
const { useLegacyProverDid, credentialDefinition, credentialOffer } = options
let createReturnObj:
| { credentialRequest: CredentialRequest; credentialRequestMetadata: CredentialRequestMetadata }
| undefined
Expand All @@ -212,8 +213,15 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService {
)
}

const isLegacyIdentifier = credentialOffer.cred_def_id.match(legacyIndyCredentialDefinitionIdRegex)

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on [library input](2) may run slow on strings with many repetitions of 'a:.:a'.
if (!isLegacyIdentifier && useLegacyProverDid) {
throw new AriesFrameworkError('Cannot use legacy prover_did with non-legacy identifiers')
}
createReturnObj = CredentialRequest.create({
entropy: anoncreds.generateNonce(), // FIXME: find a better source of entropy
entropy: !useLegacyProverDid || !isLegacyIdentifier ? anoncreds.generateNonce() : undefined,
proverDid: useLegacyProverDid
? TypedArrayEncoder.toBase58(TypedArrayEncoder.fromString(anoncreds.generateNonce().slice(0, 16)))
: undefined,
credentialDefinition: credentialDefinition as unknown as JsonObject,
credentialOffer: credentialOffer as unknown as JsonObject,
masterSecret: { value: { ms: linkSecretRecord.value } },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from '@aries-framework/anoncreds'
import { anoncreds, RevocationRegistryDefinition } from '@hyperledger/anoncreds-nodejs'

import { describeRunInNodeVersion } from '../../../../../tests/runInVersion'
import { AnonCredsCredentialDefinitionRepository } from '../../../../anoncreds/src/repository/AnonCredsCredentialDefinitionRepository'
import { AnonCredsCredentialRepository } from '../../../../anoncreds/src/repository/AnonCredsCredentialRepository'
import { AnonCredsLinkSecretRepository } from '../../../../anoncreds/src/repository/AnonCredsLinkSecretRepository'
Expand Down Expand Up @@ -56,7 +57,8 @@ const agentContext = getAgentContext({
agentConfig,
})

describe('AnonCredsRsHolderService', () => {
// FIXME: Re-include in tests when NodeJS wrapper performance is improved
describeRunInNodeVersion([18], 'AnonCredsRsHolderService', () => {
const getByCredentialIdMock = jest.spyOn(anoncredsCredentialRepositoryMock, 'getByCredentialId')

beforeEach(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { anoncreds } from '@hyperledger/anoncreds-nodejs'
import { Subject } from 'rxjs'

import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService'
import { describeRunInNodeVersion } from '../../../../../tests/runInVersion'
import { encodeCredentialValue } from '../../../../anoncreds/src/utils/credential'
import { InMemoryAnonCredsRegistry } from '../../../../anoncreds/tests/InMemoryAnonCredsRegistry'
import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers'
Expand All @@ -46,7 +47,8 @@ const agentContext = getAgentContext({
agentConfig,
})

describe('AnonCredsRsServices', () => {
// FIXME: Re-include in tests when NodeJS wrapper performance is improved
describeRunInNodeVersion([18], 'AnonCredsRsServices', () => {
test('issuance flow without revocation', async () => {
const issuerId = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt'

Expand Down
8 changes: 5 additions & 3 deletions packages/anoncreds-rs/tests/indy-flow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,15 @@ import {
import { Subject } from 'rxjs'

import { InMemoryStorageService } from '../../../tests/InMemoryStorageService'
import { describeRunInNodeVersion } from '../../../tests/runInVersion'
import { AnonCredsRegistryService } from '../../anoncreds/src/services/registry/AnonCredsRegistryService'
import { InMemoryAnonCredsRegistry } from '../../anoncreds/tests/InMemoryAnonCredsRegistry'
import { agentDependencies, getAgentConfig, getAgentContext } from '../../core/tests/helpers'
import { AnonCredsRsHolderService } from '../src/services/AnonCredsRsHolderService'
import { AnonCredsRsIssuerService } from '../src/services/AnonCredsRsIssuerService'
import { AnonCredsRsVerifierService } from '../src/services/AnonCredsRsVerifierService'

const registry = new InMemoryAnonCredsRegistry()
const registry = new InMemoryAnonCredsRegistry({ useLegacyIdentifiers: true })
const anonCredsModuleConfig = new AnonCredsModuleConfig({
registries: [registry],
})
Expand Down Expand Up @@ -69,9 +70,10 @@ const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService(
const legacyIndyProofFormatService = new LegacyIndyProofFormatService()

// This is just so we don't have to register an actually indy did (as we don't have the indy did registrar configured)
const indyDid = 'did:indy:pool:localtest:TL1EaPFCZ8Si5aUrqScBDt'
const indyDid = 'LjgpST2rjsoxYegQDRm7EL'

describe('Legacy indy format services using anoncreds-rs', () => {
// FIXME: Re-include in tests when NodeJS wrapper performance is improved
describeRunInNodeVersion([18], 'Legacy indy format services using anoncreds-rs', () => {
test('issuance and verification flow starting from proposal without negotiation and without revocation', async () => {
const schema = await anonCredsIssuerService.createSchema(agentContext, {
attrNames: ['name', 'age'],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import { AnonCredsError } from '../error'
import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal'
import { AnonCredsIssuerServiceSymbol, AnonCredsHolderServiceSymbol } from '../services'
import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService'
import { legacyIndyCredentialDefinitionIdRegex, legacyIndySchemaIdRegex } from '../utils'
import {
convertAttributesToCredentialValues,
assertCredentialValuesMatch,
Expand Down Expand Up @@ -146,10 +147,14 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic

if (!credentialDefinitionId) {
throw new AriesFrameworkError(
'No credentialDefinitionId in proposal or provided as input to accept proposal method.'
'No credential definition id in proposal or provided as input to accept proposal method.'
)
}

if (!credentialDefinitionId.match(legacyIndyCredentialDefinitionIdRegex)) {

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on [library input](2) may run slow on strings with many repetitions of 'a:.:a'. This [regular expression](1) that depends on [library input](3) may run slow on strings with many repetitions of 'a:.:a'.
throw new AriesFrameworkError(`${credentialDefinitionId} is not a valid legacy indy credential definition id`)
}

if (!attributes) {
throw new AriesFrameworkError('No attributes in proposal or provided as input to accept proposal method.')
}
Expand Down Expand Up @@ -205,7 +210,10 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic

const credOffer = attachment.getDataAsJson<AnonCredsCredentialOffer>()

if (!credOffer.schema_id || !credOffer.cred_def_id) {
if (
!credOffer.schema_id.match(legacyIndySchemaIdRegex) ||
!credOffer.cred_def_id.match(legacyIndyCredentialDefinitionIdRegex)

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on [library input](2) may run slow on strings with many repetitions of 'a:.:a'. This [regular expression](1) that depends on [library input](3) may run slow on strings with many repetitions of 'a:.:a'.
) {
throw new ProblemReportError('Invalid credential offer', {
problemCode: CredentialProblemReportReason.IssuanceAbandoned,
})
Expand All @@ -226,6 +234,11 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic

const credentialOffer = offerAttachment.getDataAsJson<AnonCredsCredentialOffer>()

if (!credentialOffer.cred_def_id.match(legacyIndyCredentialDefinitionIdRegex)) {

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on [library input](2) may run slow on strings with many repetitions of 'a:.:a'.
throw new AriesFrameworkError(
`${credentialOffer.cred_def_id} is not a valid legacy indy credential definition id`
)
}
// Get credential definition
const registry = registryService.getRegistryForIdentifier(agentContext, credentialOffer.cred_def_id)
const { credentialDefinition, resolutionMetadata } = await registry.getCredentialDefinition(
Expand All @@ -243,6 +256,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic
credentialOffer,
credentialDefinition,
linkSecretId: credentialFormats?.indy?.linkSecretId,
useLegacyProverDid: true,
})

if (!credentialRequest.prover_did) {
Expand Down
10 changes: 10 additions & 0 deletions packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ import {
checkValidCredentialValueEncoding,
encodeCredentialValue,
assertNoDuplicateGroupsNamesInProofRequest,
legacyIndyCredentialDefinitionIdRegex,
legacyIndySchemaIdRegex,
} from '../utils'

const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/[email protected]'
Expand Down Expand Up @@ -471,6 +473,10 @@ export class LegacyIndyProofFormatService implements ProofFormatService<LegacyIn
const schemas: { [key: string]: AnonCredsSchema } = {}

for (const schemaId of schemaIds) {
if (!schemaId.match(legacyIndySchemaIdRegex)) {
throw new AriesFrameworkError(`${schemaId} is not a valid legacy indy schema id`)
}

const schemaRegistry = registryService.getRegistryForIdentifier(agentContext, schemaId)
const schemaResult = await schemaRegistry.getSchema(agentContext, schemaId)

Expand Down Expand Up @@ -499,6 +505,10 @@ export class LegacyIndyProofFormatService implements ProofFormatService<LegacyIn
const credentialDefinitions: { [key: string]: AnonCredsCredentialDefinition } = {}

for (const credentialDefinitionId of credentialDefinitionIds) {
if (!credentialDefinitionId.match(legacyIndyCredentialDefinitionIdRegex)) {

Check failure

Code scanning / CodeQL

Polynomial regular expression used on uncontrolled data

This [regular expression](1) that depends on [library input](2) may run slow on strings with many repetitions of 'a:.:a'. This [regular expression](1) that depends on [library input](3) may run slow on strings with many repetitions of 'a:.:a'.
throw new AriesFrameworkError(`${credentialDefinitionId} is not a valid legacy indy credential definition id`)
}

const credentialDefinitionRegistry = registryService.getRegistryForIdentifier(
agentContext,
credentialDefinitionId
Expand Down
1 change: 1 addition & 0 deletions packages/anoncreds/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export { AnonCredsModuleConfig, AnonCredsModuleConfigOptions } from './AnonCreds
export { AnonCredsApi } from './AnonCredsApi'
export * from './AnonCredsApiOptions'
export { generateLegacyProverDidLikeString } from './utils/proverDid'
export * from './utils/legacyIndyIdentifiers'
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export interface CreateCredentialRequestOptions {
credentialOffer: AnonCredsCredentialOffer
credentialDefinition: AnonCredsCredentialDefinition
linkSecretId?: string
useLegacyProverDid?: boolean
}

export interface CreateCredentialRequestReturn {
Expand Down
60 changes: 38 additions & 22 deletions packages/anoncreds/tests/InMemoryAnonCredsRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,22 +41,26 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry {
private credentialDefinitions: Record<string, AnonCredsCredentialDefinition>
private revocationRegistryDefinitions: Record<string, AnonCredsRevocationRegistryDefinition>
private revocationStatusLists: Record<string, Record<string, AnonCredsRevocationStatusList>>
private useLegacyIdentifiers: boolean

public constructor({
existingSchemas = {},
existingCredentialDefinitions = {},
existingRevocationRegistryDefinitions = {},
existingRevocationStatusLists = {},
useLegacyIdentifiers = false,
}: {
existingSchemas?: Record<string, AnonCredsSchema>
existingCredentialDefinitions?: Record<string, AnonCredsCredentialDefinition>
existingRevocationRegistryDefinitions?: Record<string, AnonCredsRevocationRegistryDefinition>
existingRevocationStatusLists?: Record<string, Record<string, AnonCredsRevocationStatusList>>
useLegacyIdentifiers?: boolean
} = {}) {
this.schemas = existingSchemas
this.credentialDefinitions = existingCredentialDefinitions
this.revocationRegistryDefinitions = existingRevocationRegistryDefinitions
this.revocationStatusLists = existingRevocationStatusLists
this.useLegacyIdentifiers = useLegacyIdentifiers
}

public async getSchema(agentContext: AgentContext, schemaId: string): Promise<GetSchemaReturn> {
Expand Down Expand Up @@ -94,21 +98,23 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry {
agentContext: AgentContext,
options: RegisterSchemaOptions
): Promise<RegisterSchemaReturn> {
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)
let legacyIssuerId
let didIndySchemaId = ''
if (this.useLegacyIdentifiers) {
legacyIssuerId = options.schema.issuerId
} else {
const { namespace, namespaceIdentifier } = parseIndyDid(options.schema.issuerId)
legacyIssuerId = namespaceIdentifier
didIndySchemaId = getDidIndySchemaId(namespace, namespaceIdentifier, options.schema.name, options.schema.version)
this.schemas[didIndySchemaId] = options.schema
}

const legacySchemaId = getLegacySchemaId(legacyIssuerId, options.schema.name, options.schema.version)
Copy link
Contributor

Choose a reason for hiding this comment

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

The old implementation supported both for convenience, why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If schema issuerId is an unqualified did, parseIndyDid threw an error. Maybe I can do it 'automatic' (i.e. without config flag and a try-catch?)

Copy link
Contributor

Choose a reason for hiding this comment

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

It's for tests, so whatever works
Is fine. Just curious

const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId)

this.schemas[didIndySchemaId] = options.schema
this.schemas[legacySchemaId] = {
...options.schema,
issuerId: namespaceIdentifier,
issuerId: legacyIssuerId,
}

return {
Expand All @@ -121,7 +127,7 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry {
schemaState: {
state: 'finished',
schema: options.schema,
schemaId: didIndySchemaId,
schemaId: this.useLegacyIdentifiers ? legacySchemaId : didIndySchemaId,
},
}
}
Expand Down Expand Up @@ -163,24 +169,32 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry {
)
const indyLedgerSeqNo = getSeqNoFromSchemaId(legacySchemaId)

const { namespaceIdentifier, namespace } = parseIndyDid(options.credentialDefinition.issuerId)
let legacyIssuerId
let didIndyCredentialDefinitionId = ''
if (this.useLegacyIdentifiers) {
legacyIssuerId = options.credentialDefinition.issuerId
} else {
const { namespace, namespaceIdentifier } = parseIndyDid(options.credentialDefinition.issuerId)
legacyIssuerId = namespaceIdentifier
didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId(
namespace,
namespaceIdentifier,
indyLedgerSeqNo,
options.credentialDefinition.tag
)

this.credentialDefinitions[didIndyCredentialDefinitionId] = options.credentialDefinition
}

const didIndyCredentialDefinitionId = getDidIndyCredentialDefinitionId(
namespace,
namespaceIdentifier,
indyLedgerSeqNo,
options.credentialDefinition.tag
)
const legacyCredentialDefinitionId = getLegacyCredentialDefinitionId(
namespaceIdentifier,
legacyIssuerId,
indyLedgerSeqNo,
options.credentialDefinition.tag
)

this.credentialDefinitions[didIndyCredentialDefinitionId] = options.credentialDefinition
this.credentialDefinitions[legacyCredentialDefinitionId] = {
...options.credentialDefinition,
issuerId: namespaceIdentifier,
issuerId: legacyIssuerId,
schemaId: legacySchemaId,
}

Expand All @@ -190,7 +204,9 @@ export class InMemoryAnonCredsRegistry implements AnonCredsRegistry {
credentialDefinitionState: {
state: 'finished',
credentialDefinition: options.credentialDefinition,
credentialDefinitionId: didIndyCredentialDefinitionId,
credentialDefinitionId: this.useLegacyIdentifiers
? legacyCredentialDefinitionId
: didIndyCredentialDefinitionId,
},
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,10 @@ export class IndySdkHolderService implements AnonCredsHolderService {
): Promise<CreateCredentialRequestReturn> {
assertIndySdkWallet(agentContext.wallet)

if (!options.useLegacyProverDid) {
throw new AriesFrameworkError('Indy SDK only supports legacy prover did for credential requests')
}

const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository)

// We just generate a prover did like string, as it's not used for anything and we don't need
Expand Down
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -864,23 +864,23 @@
resolved "https://registry.yarnpkg.com/@hutson/parse-repository-url/-/parse-repository-url-3.0.2.tgz#98c23c950a3d9b6c8f0daed06da6c3af06981340"
integrity sha512-H9XAx3hc0BQHY6l+IFSWHDySypcXsvsuLhgYLUGywmJ5pswRVQJUHpOsobnLYp2ZUaUlKiKDrgWWhosOwAEM8Q==

"@hyperledger/anoncreds-nodejs@^0.1.0-dev.9":
version "0.1.0-dev.9"
resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-nodejs/-/anoncreds-nodejs-0.1.0-dev.9.tgz#5f09d5f147ce204abd9b3cea486c23e556cc8fdc"
integrity sha512-Ht5Gt1DfnFiRIY+AlTpe8Hcdryb+jKl79hGQ3kszdct/1JFZ70A81ssfwX0VKYTdCHt5jK5cJzVaOjOVmBKZiw==
"@hyperledger/anoncreds-nodejs@^0.1.0-dev.10":
version "0.1.0-dev.10"
resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-nodejs/-/anoncreds-nodejs-0.1.0-dev.10.tgz#80c093ecb08a277fb494399b64aad1b900c3103f"
integrity sha512-ju5mJPwuyebAPziuf+eUOwxEws02G2FHEp/qG3GV3kxtlx7THW7HVB7dMSNqhRVKCsbcNnZtWJB1UiPvWqboUg==
dependencies:
"@hyperledger/anoncreds-shared" "0.1.0-dev.9"
"@hyperledger/anoncreds-shared" "0.1.0-dev.10"
"@mapbox/node-pre-gyp" "^1.0.10"
ffi-napi "4.0.3"
node-cache "5.1.2"
ref-array-di "1.2.2"
ref-napi "3.0.3"
ref-struct-di "1.1.1"

"@hyperledger/[email protected].9", "@hyperledger/anoncreds-shared@^0.1.0-dev.9":
version "0.1.0-dev.9"
resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-shared/-/anoncreds-shared-0.1.0-dev.9.tgz#7f6a033997e2641432a51ff2b609d603b3f9ab50"
integrity sha512-xbWEB9Z9PwkxC2awx74xt1OULMxihbK2v7818Dtrrmun75gNBXF8Jdorn9+t+TEd62QLrJpVUJ1ZCKhXPx81zw==
"@hyperledger/[email protected].10", "@hyperledger/anoncreds-shared@^0.1.0-dev.10":
version "0.1.0-dev.10"
resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-shared/-/anoncreds-shared-0.1.0-dev.10.tgz#9d67f64e98ff41971644c95b03dabafd741df4df"
integrity sha512-POvcwQrUcPrwoZehQa38pN1dnjyeUlrQ6VlksbBRS8SUHJuyixZsD+d3XoumqaNfl9Z1DCjfuOgEiPlec01gXQ==

"@hyperledger/aries-askar-nodejs@^0.1.0-dev.4":
version "0.1.0-dev.4"
Expand Down