Skip to content

Commit

Permalink
feat(anoncreds): legacy indy proof format service (openwallet-foundat…
Browse files Browse the repository at this point in the history
…ion#1283)

Signed-off-by: Timo Glastra <timo@animo.id>
  • Loading branch information
TimoGlastra authored and karimStekelenburg committed Mar 17, 2023
1 parent 0916406 commit 84147f7
Show file tree
Hide file tree
Showing 58 changed files with 2,854 additions and 195 deletions.
20 changes: 10 additions & 10 deletions packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import type {
CreateLinkSecretOptions,
CreateLinkSecretReturn,
AnonCredsProofRequestRestriction,
AnonCredsRequestedAttribute,
AnonCredsRequestedPredicate,
AnonCredsCredential,
AnonCredsRequestedAttributeMatch,
AnonCredsRequestedPredicateMatch,
} from '@aries-framework/anoncreds'
import type { AgentContext, Query, SimpleQuery } from '@aries-framework/core'
import type { CredentialEntry, CredentialProve } from '@hyperledger/anoncreds-shared'
Expand Down Expand Up @@ -63,7 +63,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService {
}

public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise<AnonCredsProof> {
const { credentialDefinitions, proofRequest, requestedCredentials, schemas } = options
const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options

try {
const rsCredentialDefinitions: Record<string, CredentialDefinition> = {}
Expand All @@ -82,7 +82,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService {
const retrievedCredentials = new Map<string, AnonCredsCredentialRecord>()

const credentialEntryFromAttribute = async (
attribute: AnonCredsRequestedAttribute | AnonCredsRequestedPredicate
attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch
): Promise<{ linkSecretId: string; credentialEntry: CredentialEntry }> => {
let credentialRecord = retrievedCredentials.get(attribute.credentialId)
if (!credentialRecord) {
Expand Down Expand Up @@ -136,15 +136,15 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService {
const credentials: { linkSecretId: string; credentialEntry: CredentialEntry }[] = []

let entryIndex = 0
for (const referent in requestedCredentials.requestedAttributes) {
const attribute = requestedCredentials.requestedAttributes[referent]
for (const referent in selectedCredentials.attributes) {
const attribute = selectedCredentials.attributes[referent]
credentials.push(await credentialEntryFromAttribute(attribute))
credentialsProve.push({ entryIndex, isPredicate: false, referent, reveal: attribute.revealed })
entryIndex = entryIndex + 1
}

for (const referent in requestedCredentials.requestedPredicates) {
const predicate = requestedCredentials.requestedPredicates[referent]
for (const referent in selectedCredentials.predicates) {
const predicate = selectedCredentials.predicates[referent]
credentials.push(await credentialEntryFromAttribute(predicate))
credentialsProve.push({ entryIndex, isPredicate: true, referent, reveal: true })
entryIndex = entryIndex + 1
Expand All @@ -170,7 +170,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService {
presentationRequest: PresentationRequest.load(JSON.stringify(proofRequest)),
credentials: credentials.map((entry) => entry.credentialEntry),
credentialsProve,
selfAttest: requestedCredentials.selfAttestedAttributes,
selfAttest: selectedCredentials.selfAttestedAttributes,
masterSecret: MasterSecret.load(JSON.stringify({ value: { ms: linkSecretRecord.value } })),
})

Expand All @@ -179,7 +179,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService {
agentContext.config.logger.error(`Error creating AnonCreds Proof`, {
error,
proofRequest,
requestedCredentials,
selectedCredentials,
})
throw new AnonCredsRsError(`Error creating proof: ${error}`, { cause: error })
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { AnonCredsVerifierService, VerifyProofOptions } from '@aries-framework/anoncreds'
import type { AgentContext } from '@aries-framework/core'

import { injectable } from '@aries-framework/core'
import {
Expand All @@ -14,8 +15,8 @@ import { AnonCredsRsError } from '../errors/AnonCredsRsError'

@injectable()
export class AnonCredsRsVerifierService implements AnonCredsVerifierService {
public async verifyProof(options: VerifyProofOptions): Promise<boolean> {
const { credentialDefinitions, proof, proofRequest, revocationStates, schemas } = options
public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise<boolean> {
const { credentialDefinitions, proof, proofRequest, revocationRegistries, schemas } = options

try {
const presentation = Presentation.load(JSON.stringify(proof))
Expand All @@ -33,8 +34,8 @@ export class AnonCredsRsVerifierService implements AnonCredsVerifierService {
const revocationRegistryDefinitions: Record<string, RevocationRegistryDefinition> = {}
const lists = []

for (const revocationRegistryDefinitionId in revocationStates) {
const { definition, revocationStatusLists } = options.revocationStates[revocationRegistryDefinitionId]
for (const revocationRegistryDefinitionId in revocationRegistries) {
const { definition, revocationStatusLists } = options.revocationRegistries[revocationRegistryDefinitionId]

revocationRegistryDefinitions[revocationRegistryDefinitionId] = RevocationRegistryDefinition.load(
JSON.stringify(definition)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import type {
AnonCredsCredentialDefinition,
AnonCredsProofRequest,
AnonCredsRequestedCredentials,
AnonCredsRevocationStatusList,
AnonCredsCredential,
AnonCredsSchema,
AnonCredsSelectedCredentials,
} from '@aries-framework/anoncreds'

import {
Expand Down Expand Up @@ -191,15 +191,15 @@ describe('AnonCredsRsHolderService', () => {
revocationRegistryDefinitionId: 'phonerevregid:uri',
})

const requestedCredentials: AnonCredsRequestedCredentials = {
const selectedCredentials: AnonCredsSelectedCredentials = {
selfAttestedAttributes: { attr5_referent: 'football' },
requestedAttributes: {
attributes: {
attr1_referent: { credentialId: 'personCredId', credentialInfo: personCredentialInfo, revealed: true },
attr2_referent: { credentialId: 'phoneCredId', credentialInfo: phoneCredentialInfo, revealed: true },
attr3_referent: { credentialId: 'personCredId', credentialInfo: personCredentialInfo, revealed: true },
attr4_referent: { credentialId: 'personCredId', credentialInfo: personCredentialInfo, revealed: true },
},
requestedPredicates: {
predicates: {
predicate1_referent: { credentialId: 'personCredId', credentialInfo: personCredentialInfo },
},
}
Expand Down Expand Up @@ -246,7 +246,7 @@ describe('AnonCredsRsHolderService', () => {
'phonecreddef:uri': phoneCredentialDefinition as AnonCredsCredentialDefinition,
},
proofRequest,
requestedCredentials,
selectedCredentials,
schemas: {
'phoneschema:uri': { attrNames: ['phoneNumber'], issuerId: 'issuer:uri', name: 'phoneschema', version: '1' },
'personschema:uri': {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { anoncreds } from '@hyperledger/anoncreds-nodejs'
import { Subject } from 'rxjs'

import { InMemoryStorageService } from '../../../../../tests/InMemoryStorageService'
import { encode } from '../../../../anoncreds/src/utils/credential'
import { encodeCredentialValue } from '../../../../anoncreds/src/utils/credential'
import { InMemoryAnonCredsRegistry } from '../../../../anoncreds/tests/InMemoryAnonCredsRegistry'
import { agentDependencies, getAgentConfig, getAgentContext } from '../../../../core/tests/helpers'
import { AnonCredsRsHolderService } from '../AnonCredsRsHolderService'
Expand Down Expand Up @@ -145,7 +145,10 @@ describe('AnonCredsRsServices', () => {
const { credential } = await anonCredsIssuerService.createCredential(agentContext, {
credentialOffer,
credentialRequest: credentialRequestState.credentialRequest,
credentialValues: { name: { raw: 'John', encoded: encode('John') }, age: { raw: '25', encoded: encode('25') } },
credentialValues: {
name: { raw: 'John', encoded: encodeCredentialValue('John') },
age: { raw: '25', encoded: encodeCredentialValue('25') },
},
})

const credentialId = 'holderCredentialId'
Expand Down Expand Up @@ -197,12 +200,12 @@ describe('AnonCredsRsServices', () => {
const proof = await anonCredsHolderService.createProof(agentContext, {
credentialDefinitions: { [credentialDefinitionState.credentialDefinitionId]: credentialDefinition },
proofRequest,
requestedCredentials: {
requestedAttributes: {
selectedCredentials: {
attributes: {
attr1_referent: { credentialId, credentialInfo, revealed: true },
attr2_referent: { credentialId, credentialInfo, revealed: true },
},
requestedPredicates: {
predicates: {
predicate1_referent: { credentialId, credentialInfo },
},
selfAttestedAttributes: {},
Expand All @@ -211,12 +214,12 @@ describe('AnonCredsRsServices', () => {
revocationRegistries: {},
})

const verifiedProof = await anonCredsVerifierService.verifyProof({
const verifiedProof = await anonCredsVerifierService.verifyProof(agentContext, {
credentialDefinitions: { [credentialDefinitionState.credentialDefinitionId]: credentialDefinition },
proof,
proofRequest,
schemas: { [schemaState.schemaId]: schema },
revocationStates: {},
revocationRegistries: {},
})

expect(verifiedProof).toBeTruthy()
Expand Down
89 changes: 82 additions & 7 deletions packages/anoncreds-rs/tests/indy-flow.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import type { Wallet } from '@aries-framework/core'

import {
AnonCredsModuleConfig,
LegacyIndyCredentialFormatService,
AnonCredsHolderServiceSymbol,
AnonCredsIssuerServiceSymbol,
AnonCredsVerifierServiceSymbol,
AnonCredsRegistryService,
AnonCredsSchemaRecord,
AnonCredsSchemaRepository,
AnonCredsCredentialDefinitionRepository,
Expand All @@ -15,16 +16,20 @@ import {
AnonCredsKeyCorrectnessProofRecord,
AnonCredsLinkSecretRepository,
AnonCredsLinkSecretRecord,
LegacyIndyProofFormatService,
} from '@aries-framework/anoncreds'
import {
CredentialState,
CredentialExchangeRecord,
CredentialPreviewAttribute,
InjectionSymbols,
ProofState,
ProofExchangeRecord,
} from '@aries-framework/core'
import { Subject } from 'rxjs'

import { InMemoryStorageService } from '../../../tests/InMemoryStorageService'
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'
Expand All @@ -36,11 +41,13 @@ const anonCredsModuleConfig = new AnonCredsModuleConfig({
registries: [registry],
})

const agentConfig = getAgentConfig('LegacyIndyCredentialFormatService')
const agentConfig = getAgentConfig('LegacyIndyCredentialFormatService using anoncreds-rs')
const anonCredsVerifierService = new AnonCredsRsVerifierService()
const anonCredsHolderService = new AnonCredsRsHolderService()
const anonCredsIssuerService = new AnonCredsRsIssuerService()

const wallet = { generateNonce: () => Promise.resolve('947121108704767252195123') } as Wallet

const inMemoryStorageService = new InMemoryStorageService()
const agentContext = getAgentContext({
registerInstances: [
Expand All @@ -54,23 +61,25 @@ const agentContext = getAgentContext({
[AnonCredsModuleConfig, anonCredsModuleConfig],
],
agentConfig,
wallet,
})

const legacyIndyCredentialFormatService = new LegacyIndyCredentialFormatService()
const legacyIndyProofFormatService = new LegacyIndyProofFormatService()

describe('LegacyIndyCredentialFormatService using anoncreds-rs', () => {
test('issuance flow starting from proposal without negotiation and without revocation', async () => {
// 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 = 'TL1EaPFCZ8Si5aUrqScBDt'
// 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 = 'TL1EaPFCZ8Si5aUrqScBDt'

describe('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'],
issuerId: indyDid,
name: 'Employee Credential',
version: '1.0.0',
})

const { schemaState, schemaMetadata } = await registry.registerSchema(agentContext, {
const { schemaState } = await registry.registerSchema(agentContext, {
schema,
options: {},
})
Expand Down Expand Up @@ -273,5 +282,71 @@ describe('LegacyIndyCredentialFormatService using anoncreds-rs', () => {
credentialDefinitionId: credentialDefinitionState.credentialDefinitionId,
},
})

const holderProofRecord = new ProofExchangeRecord({
protocolVersion: 'v1',
state: ProofState.ProposalSent,
threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38',
})
const verifierProofRecord = new ProofExchangeRecord({
protocolVersion: 'v1',
state: ProofState.ProposalReceived,
threadId: '4f5659a4-1aea-4f42-8c22-9a9985b35e38',
})

const { attachment: proofProposalAttachment } = await legacyIndyProofFormatService.createProposal(agentContext, {
proofFormats: {
indy: {
attributes: [
{
name: 'name',
credentialDefinitionId: credentialDefinitionState.credentialDefinitionId,
value: 'John',
referent: '1',
},
],
predicates: [
{
credentialDefinitionId: credentialDefinitionState.credentialDefinitionId,
name: 'age',
predicate: '>=',
threshold: 18,
},
],
name: 'Proof Request',
version: '1.0',
},
},
proofRecord: holderProofRecord,
})

await legacyIndyProofFormatService.processProposal(agentContext, {
attachment: proofProposalAttachment,
proofRecord: verifierProofRecord,
})

const { attachment: proofRequestAttachment } = await legacyIndyProofFormatService.acceptProposal(agentContext, {
proofRecord: verifierProofRecord,
proposalAttachment: proofProposalAttachment,
})

await legacyIndyProofFormatService.processRequest(agentContext, {
attachment: proofRequestAttachment,
proofRecord: holderProofRecord,
})

const { attachment: proofAttachment } = await legacyIndyProofFormatService.acceptRequest(agentContext, {
proofRecord: holderProofRecord,
requestAttachment: proofRequestAttachment,
proposalAttachment: proofProposalAttachment,
})

const isValid = await legacyIndyProofFormatService.processPresentation(agentContext, {
attachment: proofAttachment,
proofRecord: verifierProofRecord,
requestAttachment: proofRequestAttachment,
})

expect(isValid).toBe(true)
})
})
4 changes: 3 additions & 1 deletion packages/anoncreds/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
"dependencies": {
"@aries-framework/core": "0.3.3",
"@aries-framework/node": "0.3.3",
"bn.js": "^5.2.1"
"bn.js": "^5.2.1",
"class-transformer": "0.5.1",
"class-validator": "0.13.1"
},
"devDependencies": {
"indy-sdk": "^1.16.0-dev-1636",
Expand Down
30 changes: 16 additions & 14 deletions packages/anoncreds/src/formats/AnonCredsCredentialFormat.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import type { AnonCredsCredential, AnonCredsCredentialOffer, AnonCredsCredentialRequest } from '../models'
import type { CredentialPreviewAttributeOptions, CredentialFormat, LinkedAttachment } from '@aries-framework/core'

export interface AnonCredsCredentialProposalFormat {
schema_issuer_id?: string
schema_name?: string
schema_version?: string
schema_id?: string

cred_def_id?: string
issuer_id?: string

// TODO: we don't necessarily need to include these in the AnonCreds Format RFC
// as it's a new one and we can just forbid the use of legacy properties
schema_issuer_did?: string
issuer_did?: string
}

/**
* This defines the module payload for calling CredentialsApi.createProposal
* or CredentialsApi.negotiateOffer
Expand Down Expand Up @@ -70,20 +85,7 @@ export interface AnonCredsCredentialFormat extends CredentialFormat {
// Format data is based on RFC 0592
// https://github.com/hyperledger/aries-rfcs/tree/main/features/0592-indy-attachments
formatData: {
proposal: {
schema_issuer_id?: string
schema_name?: string
schema_version?: string
schema_id?: string

cred_def_id?: string
issuer_id?: string

// TODO: we don't necessarily need to include these in the AnonCreds Format RFC
// as it's a new one and we can just forbid the use of legacy properties
schema_issuer_did?: string
issuer_did?: string
}
proposal: AnonCredsCredentialProposalFormat
offer: AnonCredsCredentialOffer
request: AnonCredsCredentialRequest
credential: AnonCredsCredential
Expand Down
Loading

0 comments on commit 84147f7

Please sign in to comment.