From 974bae102bf41fc41829c2f9cf4eecf46728c7ad Mon Sep 17 00:00:00 2001 From: Abdulbois Date: Mon, 20 Nov 2023 16:44:35 +0500 Subject: [PATCH 1/8] demo: Replace with W3C calls --- demo/package.json | 10 +-- demo/src/BaseAgent.ts | 3 + packages/anoncreds-rs/package.json | 4 +- .../src/services/AnonCredsRsHolderService.ts | 89 +++++++++++-------- .../src/services/AnonCredsRsIssuerService.ts | 12 +-- .../services/AnonCredsRsVerifierService.ts | 6 +- .../src/services/__tests__/helpers.ts | 14 +-- .../AnonCredsCredentialFormatService.ts | 64 +++++++------ .../LegacyIndyCredentialFormatService.ts | 4 +- .../repository/AnonCredsCredentialRecord.ts | 18 ++-- .../services/AnonCredsHolderServiceOptions.ts | 4 +- .../services/IndySdkHolderService.ts | 19 ++-- 12 files changed, 143 insertions(+), 104 deletions(-) diff --git a/demo/package.json b/demo/package.json index 4ffbef505b..ae46d4d6ab 100644 --- a/demo/package.json +++ b/demo/package.json @@ -10,23 +10,19 @@ "license": "Apache-2.0", "scripts": { "alice": "ts-node src/AliceInquirer.ts", - "faber": "ts-node src/FaberInquirer.ts", + "faber": "ts-node --logError src/FaberInquirer.ts", "refresh": "rm -rf ./node_modules ./yarn.lock && yarn" }, "dependencies": { "@hyperledger/indy-vdr-nodejs": "^0.2.0-dev.5", - "@hyperledger/anoncreds-nodejs": "^0.2.0-dev.4", + "@hyperledger/anoncreds-nodejs": "file:/Users/abdulbois/Documents/anoncreds-rs/wrappers/javascript/packages/anoncreds-nodejs/build", "@hyperledger/aries-askar-nodejs": "^0.2.0-dev.1", "inquirer": "^8.2.5" }, "devDependencies": { "@aries-framework/anoncreds": "*", "@aries-framework/anoncreds-rs": "*", - "@aries-framework/askar": "*", - "@aries-framework/core": "*", - "@aries-framework/indy-sdk": "*", - "@aries-framework/indy-vdr": "*", - "@aries-framework/cheqd": "*", + "@aries-framework/node": "*", "@types/figlet": "^1.5.4", "@types/indy-sdk": "^1.16.26", diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index c2e787e32a..af11c3b742 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -31,6 +31,8 @@ import { CredentialsModule, Agent, HttpOutboundTransport, + ConsoleLogger, + LogLevel, } from '@aries-framework/core' import { IndySdkAnonCredsRegistry, IndySdkModule, IndySdkSovDidResolver } from '@aries-framework/indy-sdk' import { IndyVdrIndyDidResolver, IndyVdrAnonCredsRegistry, IndyVdrModule } from '@aries-framework/indy-vdr' @@ -84,6 +86,7 @@ export class BaseAgent { id: name, key: name, }, + logger: new ConsoleLogger(LogLevel.trace), endpoints: [`http://localhost:${this.port}`], } satisfies InitConfig diff --git a/packages/anoncreds-rs/package.json b/packages/anoncreds-rs/package.json index aaafaf228d..37a2c58eb6 100644 --- a/packages/anoncreds-rs/package.json +++ b/packages/anoncreds-rs/package.json @@ -32,8 +32,8 @@ "tsyringe": "^4.8.0" }, "devDependencies": { - "@hyperledger/anoncreds-nodejs": "^0.2.0-dev.4", - "@hyperledger/anoncreds-shared": "^0.2.0-dev.4", + "@hyperledger/anoncreds-nodejs": "file:///Users/abdulbois/Documents/anoncreds-rs/wrappers/javascript/packages/anoncreds-nodejs/build", + "@hyperledger/anoncreds-shared": "file:///Users/abdulbois/Documents/anoncreds-rs/wrappers/javascript/packages/anoncreds-shared/build", "@types/ref-array-di": "^1.2.6", "@types/ref-struct-di": "^1.1.10", "reflect-metadata": "^0.1.13", diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index 7249da2662..587abae94f 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -21,10 +21,10 @@ import type { } from '@aries-framework/anoncreds' import type { AgentContext, Query, SimpleQuery } from '@aries-framework/core' import type { - CredentialEntry, CredentialProve, CredentialRequestMetadata, JsonObject, + W3CCredentialEntry, } from '@hyperledger/anoncreds-shared' import { @@ -38,11 +38,11 @@ import { } from '@aries-framework/anoncreds' import { AriesFrameworkError, JsonTransformer, TypedArrayEncoder, injectable, utils } from '@aries-framework/core' import { - Credential, - CredentialRequest, + W3CCredential, + W3CCredentialRequest, CredentialRevocationState, LinkSecret, - Presentation, + W3CPresentation, RevocationRegistryDefinition, RevocationStatusList, anoncreds, @@ -66,7 +66,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options - let presentation: Presentation | undefined + let presentation: W3CPresentation | undefined try { const rsCredentialDefinitions: Record = {} for (const credDefId in credentialDefinitions) { @@ -85,14 +85,16 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { const credentialEntryFromAttribute = async ( attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch - ): Promise<{ linkSecretId: string; credentialEntry: CredentialEntry }> => { + ): Promise<{ linkSecretId: string; credentialEntry: W3CCredentialEntry }> => { let credentialRecord = retrievedCredentials.get(attribute.credentialId) if (!credentialRecord) { credentialRecord = await credentialRepository.getByCredentialId(agentContext, attribute.credentialId) retrievedCredentials.set(attribute.credentialId, credentialRecord) } - const revocationRegistryDefinitionId = credentialRecord.credential.rev_reg_id + const w3cCred = W3CCredential.fromJson(credentialRecord.credential) + + const revocationRegistryDefinitionId = w3cCred.revocationRegistryId const revocationRegistryIndex = credentialRecord.credentialRevocationId // TODO: Check if credential has a revocation registry id (check response from anoncreds-rs API, as it is @@ -141,7 +143,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } const credentialsProve: CredentialProve[] = [] - const credentials: { linkSecretId: string; credentialEntry: CredentialEntry }[] = [] + const credentials: { linkSecretId: string; credentialEntry: W3CCredentialEntry }[] = [] let entryIndex = 0 for (const referent in selectedCredentials.attributes) { @@ -172,13 +174,13 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { throw new AnonCredsRsError('Link Secret value not stored') } - presentation = Presentation.create({ + presentation = W3CPresentation.create({ credentialDefinitions: rsCredentialDefinitions, schemas: rsSchemas, presentationRequest: proofRequest as unknown as JsonObject, credentials: credentials.map((entry) => entry.credentialEntry), credentialsProve, - selfAttest: selectedCredentials.selfAttestedAttributes, + // selfAttest: selectedCredentials.selfAttestedAttributes, linkSecret: linkSecretRecord.value, }) @@ -194,7 +196,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { ): Promise { const { useLegacyProverDid, credentialDefinition, credentialOffer } = options let createReturnObj: - | { credentialRequest: CredentialRequest; credentialRequestMetadata: CredentialRequestMetadata } + | { credentialRequest: W3CCredentialRequest; credentialRequestMetadata: CredentialRequestMetadata } | undefined try { const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) @@ -224,7 +226,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { if (!isLegacyIdentifier && useLegacyProverDid) { throw new AriesFrameworkError('Cannot use legacy prover_did with non-legacy identifiers') } - createReturnObj = CredentialRequest.create({ + createReturnObj = W3CCredentialRequest.create({ entropy: !useLegacyProverDid || !isLegacyIdentifier ? anoncreds.generateNonce() : undefined, proverDid: useLegacyProverDid ? TypedArrayEncoder.toBase58(TypedArrayEncoder.fromString(anoncreds.generateNonce().slice(0, 16))) @@ -261,10 +263,10 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { const credentialId = options.credentialId ?? utils.uuid() - let credentialObj: Credential | undefined - let processedCredential: Credential | undefined + let credentialObj: W3CCredential | undefined + let processedCredential: W3CCredential | undefined try { - credentialObj = Credential.fromJson(credential as unknown as JsonObject) + credentialObj = credential processedCredential = credentialObj.process({ credentialDefinition: credentialDefinition as unknown as JsonObject, credentialRequestMetadata: credentialRequestMetadata as unknown as JsonObject, @@ -276,12 +278,12 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { const methodName = agentContext.dependencyManager .resolve(AnonCredsRegistryService) - .getRegistryForIdentifier(agentContext, credential.cred_def_id).methodName + .getRegistryForIdentifier(agentContext, credential.credentialDefinitionId).methodName await credentialRepository.save( agentContext, new AnonCredsCredentialRecord({ - credential: processedCredential.toJson() as unknown as AnonCredsCredential, + credential: processedCredential.toJson(), credentialId, linkSecretId: linkSecretRecord.linkSecretId, issuerId: options.credentialDefinition.issuerId, @@ -309,16 +311,20 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { .getByCredentialId(agentContext, options.credentialId) const attributes: { [key: string]: string } = {} - for (const attribute in credentialRecord.credential.values) { - attributes[attribute] = credentialRecord.credential.values[attribute].raw + const w3cCred = W3CCredential.fromJson(credentialRecord.credential) + + const cred = w3cCred.toLegacy().toJson() as unknown as AnonCredsCredential + + for (const attribute in cred.values) { + attributes[attribute] = cred.values[attribute].raw } return { attributes, - credentialDefinitionId: credentialRecord.credential.cred_def_id, + credentialDefinitionId: w3cCred.credentialDefinitionId, credentialId: credentialRecord.credentialId, - schemaId: credentialRecord.credential.schema_id, + schemaId: w3cCred.schemaId, credentialRevocationId: credentialRecord.credentialRevocationId, - revocationRegistryId: credentialRecord.credential.rev_reg_id, + revocationRegistryId: w3cCred.revocationRegistryId, methodName: credentialRecord.methodName, } } @@ -339,17 +345,20 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { methodName: options.methodName, }) - return credentialRecords.map((credentialRecord) => ({ - attributes: Object.fromEntries( - Object.entries(credentialRecord.credential.values).map(([key, value]) => [key, value.raw]) - ), - credentialDefinitionId: credentialRecord.credential.cred_def_id, - credentialId: credentialRecord.credentialId, - schemaId: credentialRecord.credential.schema_id, - credentialRevocationId: credentialRecord.credentialRevocationId, - revocationRegistryId: credentialRecord.credential.rev_reg_id, - methodName: credentialRecord.methodName, - })) + return credentialRecords.map((credentialRecord) => { + const w3cCred = W3CCredential.fromJson(credentialRecord.credential) + + const cred = w3cCred.toLegacy().toJson() as unknown as AnonCredsCredential + return { + attributes: Object.fromEntries(Object.entries(cred.values).map(([key, value]) => [key, value.raw])), + credentialDefinitionId: w3cCred.credentialDefinitionId, + credentialId: credentialRecord.credentialId, + schemaId: w3cCred.schemaId, + credentialRevocationId: credentialRecord.credentialRevocationId, + revocationRegistryId: w3cCred.revocationRegistryId, + methodName: credentialRecord.methodName, + } + }) } public async deleteCredential(agentContext: AgentContext, credentialId: string): Promise { @@ -402,18 +411,22 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { }) return credentials.map((credentialRecord) => { + const w3cCred = W3CCredential.fromJson(credentialRecord.credential) + const attributes: { [key: string]: string } = {} - for (const attribute in credentialRecord.credential.values) { - attributes[attribute] = credentialRecord.credential.values[attribute].raw + const cred = w3cCred.toLegacy().toJson() as unknown as AnonCredsCredential + + for (const attribute in cred.values) { + attributes[attribute] = cred.values[attribute].raw } return { credentialInfo: { attributes, - credentialDefinitionId: credentialRecord.credential.cred_def_id, + credentialDefinitionId: w3cCred.credentialDefinitionId, credentialId: credentialRecord.credentialId, - schemaId: credentialRecord.credential.schema_id, + schemaId: w3cCred.schemaId, credentialRevocationId: credentialRecord.credentialRevocationId, - revocationRegistryId: credentialRecord.credential.rev_reg_id, + revocationRegistryId: w3cCred.revocationRegistryId, methodName: credentialRecord.methodName, }, interval: proofRequest.non_revoked, diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts index 383c6e94e7..ab98a23aa6 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts @@ -24,7 +24,7 @@ import { AnonCredsCredentialDefinitionRepository, } from '@aries-framework/anoncreds' import { injectable, AriesFrameworkError } from '@aries-framework/core' -import { Credential, CredentialDefinition, CredentialOffer, Schema } from '@hyperledger/anoncreds-shared' +import { W3CCredential, CredentialDefinition, W3CCredentialOffer, Schema } from '@hyperledger/anoncreds-shared' import { AnonCredsRsError } from '../errors/AnonCredsRsError' @@ -89,7 +89,7 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { ): Promise { const { credentialDefinitionId } = options - let credentialOffer: CredentialOffer | undefined + let credentialOffer: W3CCredentialOffer | undefined try { // The getByCredentialDefinitionId supports both qualified and unqualified identifiers, even though the // record is always stored using the qualified identifier. @@ -116,7 +116,7 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { schemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) } - credentialOffer = CredentialOffer.create({ + credentialOffer = W3CCredentialOffer.create({ credentialDefinitionId, keyCorrectnessProof: keyCorrectnessProofRecord?.value, schemaId, @@ -134,7 +134,7 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { ): Promise { const { tailsFilePath, credentialOffer, credentialRequest, credentialValues, revocationRegistryId } = options - let credential: Credential | undefined + let credential: W3CCredential | undefined try { if (revocationRegistryId || tailsFilePath) { throw new AriesFrameworkError('Revocation not supported yet') @@ -172,12 +172,12 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { } } - credential = Credential.create({ + credential = W3CCredential.create({ credentialDefinition: credentialDefinitionRecord.credentialDefinition as unknown as JsonObject, credentialOffer: credentialOffer as unknown as JsonObject, credentialRequest: credentialRequest as unknown as JsonObject, revocationRegistryId, - attributeEncodedValues, + // attributeEncodedValues, attributeRawValues, credentialDefinitionPrivate: credentialDefinitionPrivateRecord.value, }) diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts index 26573309ff..73e662bb29 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts @@ -3,16 +3,16 @@ import type { AgentContext } from '@aries-framework/core' import type { JsonObject } from '@hyperledger/anoncreds-shared' import { injectable } from '@aries-framework/core' -import { Presentation } from '@hyperledger/anoncreds-shared' +import { W3CPresentation } from '@hyperledger/anoncreds-shared' @injectable() export class AnonCredsRsVerifierService implements AnonCredsVerifierService { public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { const { credentialDefinitions, proof, proofRequest, revocationRegistries, schemas } = options - let presentation: Presentation | undefined + let presentation: W3CPresentation | undefined try { - presentation = Presentation.fromJson(proof as unknown as JsonObject) + presentation = W3CPresentation.fromJson(proof as unknown as JsonObject) const rsCredentialDefinitions: Record = {} for (const credDefId in credentialDefinitions) { diff --git a/packages/anoncreds-rs/src/services/__tests__/helpers.ts b/packages/anoncreds-rs/src/services/__tests__/helpers.ts index 47af6c909c..ae6c1bd6d0 100644 --- a/packages/anoncreds-rs/src/services/__tests__/helpers.ts +++ b/packages/anoncreds-rs/src/services/__tests__/helpers.ts @@ -8,10 +8,10 @@ import type { JsonObject } from '@hyperledger/anoncreds-nodejs' import { anoncreds, - Credential, + W3CCredential, CredentialDefinition, - CredentialOffer, - CredentialRequest, + W3CCredentialOffer, + W3CCredentialRequest, CredentialRevocationConfig, LinkSecret, RevocationRegistryDefinition, @@ -62,7 +62,7 @@ export function createCredentialDefinition(options: { attributeNames: string[]; * Creates a valid credential offer and returns itsf */ export function createCredentialOffer(keyCorrectnessProof: Record) { - const credentialOffer = CredentialOffer.create({ + const credentialOffer = W3CCredentialOffer.create({ credentialDefinitionId: 'creddef:uri', keyCorrectnessProof, schemaId: 'schema:uri', @@ -105,13 +105,13 @@ export function createCredentialForHolder(options: { revocationRegistryDefinitionId, } = options - const credentialOffer = CredentialOffer.create({ + const credentialOffer = W3CCredentialOffer.create({ credentialDefinitionId, keyCorrectnessProof, schemaId, }) - const { credentialRequest, credentialRequestMetadata } = CredentialRequest.create({ + const { credentialRequest, credentialRequestMetadata } = W3CCredentialRequest.create({ entropy: 'some-entropy', credentialDefinition, credentialOffer, @@ -138,7 +138,7 @@ export function createCredentialForHolder(options: { revocationRegistryDefinitionId: 'mock:uri', }) - const credentialObj = Credential.create({ + const credentialObj = W3CCredential.create({ credentialDefinition, credentialDefinitionPrivate, credentialOffer, diff --git a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts index 28d7d47185..b262f4d467 100644 --- a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts @@ -41,6 +41,11 @@ import { JsonTransformer, } from '@aries-framework/core' +import { + JsonObject, + W3CCredential +} from '@hyperledger/anoncreds-shared' + import { AnonCredsError } from '../error' import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' import { AnonCredsIssuerServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' @@ -358,43 +363,44 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService ) } - const anonCredsCredential = attachment.getDataAsJson() + const jsonObj: JsonObject = attachment.getDataAsJson() + const anonCredsCredential: W3CCredential = W3CCredential.fromJson(jsonObj) const credentialDefinitionResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.cred_def_id) - .getCredentialDefinition(agentContext, anonCredsCredential.cred_def_id) + .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialDefinitionId) + .getCredentialDefinition(agentContext, anonCredsCredential.credentialDefinitionId) if (!credentialDefinitionResult.credentialDefinition) { throw new AriesFrameworkError( - `Unable to resolve credential definition ${anonCredsCredential.cred_def_id}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` + `Unable to resolve credential definition ${anonCredsCredential.credentialDefinitionId}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` ) } const schemaResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.cred_def_id) - .getSchema(agentContext, anonCredsCredential.schema_id) + .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialDefinitionId) + .getSchema(agentContext, anonCredsCredential.schemaId) if (!schemaResult.schema) { throw new AriesFrameworkError( - `Unable to resolve schema ${anonCredsCredential.schema_id}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + `Unable to resolve schema ${anonCredsCredential.schemaId}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` ) } // Resolve revocation registry if credential is revocable let revocationRegistryResult: null | GetRevocationRegistryDefinitionReturn = null - if (anonCredsCredential.rev_reg_id) { + if (anonCredsCredential.revocationRegistryId) { revocationRegistryResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.rev_reg_id) - .getRevocationRegistryDefinition(agentContext, anonCredsCredential.rev_reg_id) + .getRegistryForIdentifier(agentContext, anonCredsCredential.revocationRegistryId) + .getRevocationRegistryDefinition(agentContext, anonCredsCredential.revocationRegistryId) if (!revocationRegistryResult.revocationRegistryDefinition) { throw new AriesFrameworkError( - `Unable to resolve revocation registry definition ${anonCredsCredential.rev_reg_id}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` + `Unable to resolve revocation registry definition ${anonCredsCredential.revocationRegistryId}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` ) } } // assert the credential values match the offer values - const recordCredentialValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) - assertCredentialValuesMatch(anonCredsCredential.values, recordCredentialValues) + // const recordCredentialValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) + // assertCredentialValuesMatch(anonCredsCredential.values, recordCredentialValues) const credentialId = await anonCredsHolderService.storeCredential(agentContext, { credentialId: utils.uuid(), @@ -412,23 +418,27 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService }) // If the credential is revocable, store the revocation identifiers in the credential record - if (anonCredsCredential.rev_reg_id) { - const credential = await anonCredsHolderService.getCredential(agentContext, { credentialId }) + try { + if (anonCredsCredential.revocationRegistryId) { + const credential = await anonCredsHolderService.getCredential(agentContext, {credentialId}) + + credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { + credentialRevocationId: credential.credentialRevocationId, + revocationRegistryId: credential.revocationRegistryId, + }) + credentialRecord.setTags({ + anonCredsRevocationRegistryId: credential.revocationRegistryId, + anonCredsCredentialRevocationId: credential.credentialRevocationId, + }) + } - credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { - credentialRevocationId: credential.credentialRevocationId, - revocationRegistryId: credential.revocationRegistryId, - }) - credentialRecord.setTags({ - anonCredsRevocationRegistryId: credential.revocationRegistryId, - anonCredsCredentialRevocationId: credential.credentialRevocationId, + credentialRecord.credentials.push({ + credentialRecordType: this.credentialRecordType, + credentialRecordId: credentialId, }) + } catch (_) { + /* empty */ } - - credentialRecord.credentials.push({ - credentialRecordType: this.credentialRecordType, - credentialRecordId: credentialId, - }) } public supportsFormat(format: string): boolean { diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts index 98a45b70bc..7434662aed 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -56,6 +56,8 @@ import { isUnqualifiedCredentialDefinitionId, isUnqualifiedSchemaId } from '../u import { AnonCredsCredentialMetadataKey, AnonCredsCredentialRequestMetadataKey } from '../utils/metadata' import { generateLegacyProverDidLikeString } from '../utils/proverDid' +import {JsonObject, W3CCredential} from "@hyperledger/anoncreds-shared"; + const INDY_CRED_ABSTRACT = 'hlindy/cred-abstract@v2.0' const INDY_CRED_REQUEST = 'hlindy/cred-req@v2.0' const INDY_CRED_FILTER = 'hlindy/cred-filter@v2.0' @@ -417,7 +419,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic const credentialId = await anonCredsHolderService.storeCredential(agentContext, { credentialId: utils.uuid(), credentialRequestMetadata, - credential: anonCredsCredential, + credential: W3CCredential.fromJson(anonCredsCredential as unknown as JsonObject), credentialDefinitionId: credentialDefinitionResult.credentialDefinitionId, credentialDefinition: credentialDefinitionResult.credentialDefinition, schema: schemaResult.schema, diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts index d9627b6fb3..a55e795fb6 100644 --- a/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts +++ b/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts @@ -1,12 +1,12 @@ -import type { AnonCredsCredential } from '../models' import type { Tags } from '@aries-framework/core' import { BaseRecord, utils } from '@aries-framework/core' +import { JsonObject, W3CCredential } from '@hyperledger/anoncreds-shared' export interface AnonCredsCredentialRecordProps { id?: string createdAt?: Date - credential: AnonCredsCredential + credential: JsonObject credentialId: string credentialRevocationId?: string linkSecretId: string @@ -48,7 +48,7 @@ export class AnonCredsCredentialRecord extends BaseRecord< public readonly credentialId!: string public readonly credentialRevocationId?: string public readonly linkSecretId!: string - public readonly credential!: AnonCredsCredential + public readonly credential!: JsonObject /** * AnonCreds method name. We don't use names explicitly from the registry (there's no identifier for a registry) @@ -77,18 +77,22 @@ export class AnonCredsCredentialRecord extends BaseRecord< } public getTags() { + const w3cCred = W3CCredential.fromJson(this.credential) const tags: Tags = { ...this._tags, - credentialDefinitionId: this.credential.cred_def_id, - schemaId: this.credential.schema_id, + credentialDefinitionId: w3cCred.credentialDefinitionId, + schemaId: w3cCred.schemaId, credentialId: this.credentialId, credentialRevocationId: this.credentialRevocationId, - revocationRegistryId: this.credential.rev_reg_id, + revocationRegistryId: w3cCred.revocationRegistryId, linkSecretId: this.linkSecretId, methodName: this.methodName, } + const cred = w3cCred.toLegacy().toJson() - for (const [key, value] of Object.entries(this.credential.values)) { + // @ts-ignore + for (const [key, value] of Object.entries(cred.values)) { + // @ts-ignore tags[`attr::${key}::value`] = value.raw tags[`attr::${key}::marker`] = true } diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index a657279715..cc99b2ef53 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -17,6 +17,8 @@ import type { AnonCredsSchema, } from '../models/registry' +import { W3CCredential } from '@hyperledger/anoncreds-shared' + export interface AnonCredsAttributeInfo { name?: string names?: string[] @@ -45,7 +47,7 @@ export interface CreateProofOptions { export interface StoreCredentialOptions { credentialRequestMetadata: AnonCredsCredentialRequestMetadata - credential: AnonCredsCredential + credential: W3CCredential credentialDefinition: AnonCredsCredentialDefinition schema: AnonCredsSchema credentialDefinitionId: string diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index 0557305ea7..07f7f40fed 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -12,7 +12,7 @@ import type { AnonCredsSelectedCredentials, CreateLinkSecretOptions, CreateLinkSecretReturn, - GetCredentialsOptions, + GetCredentialsOptions, AnonCredsCredential, } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' import type { @@ -172,9 +172,18 @@ export class IndySdkHolderService implements AnonCredsHolderService { public async storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { assertIndySdkWallet(agentContext.wallet) assertAllUnqualified({ - schemaIds: [options.credentialDefinition.schemaId, options.credential.schema_id], - credentialDefinitionIds: [options.credentialDefinitionId, options.credential.cred_def_id], - revocationRegistryIds: [options.revocationRegistry?.id, options.credential.rev_reg_id], + schemaIds: [ + options.credentialDefinition.schemaId, + (options.credential as unknown as AnonCredsCredential).schema_id, + ], + credentialDefinitionIds: [ + options.credentialDefinitionId, + (options.credential as unknown as AnonCredsCredential).cred_def_id, + ], + revocationRegistryIds: [ + options.revocationRegistry?.id, + (options.credential as unknown as AnonCredsCredential).rev_reg_id, + ], }) const indyRevocationRegistryDefinition = options.revocationRegistry @@ -189,7 +198,7 @@ export class IndySdkHolderService implements AnonCredsHolderService { agentContext.wallet.handle, options.credentialId ?? null, indySdkCredentialRequestMetadataFromAnonCreds(options.credentialRequestMetadata), - options.credential, + options.credential as unknown as AnonCredsCredential, indySdkCredentialDefinitionFromAnonCreds(options.credentialDefinitionId, options.credentialDefinition), indyRevocationRegistryDefinition ) From 3e6113e18dcd5aee8e8089a46630cf8e5982c551 Mon Sep 17 00:00:00 2001 From: Abdulbois Date: Wed, 22 Nov 2023 13:30:20 +0500 Subject: [PATCH 2/8] feat: Add AnonCredsW3CCredential and replace calls with new type --- .../src/services/AnonCredsRsHolderService.ts | 57 +++++++++---------- packages/anoncreds/src/models/exchange.ts | 38 +++++++++++++ .../repository/AnonCredsCredentialRecord.ts | 21 +++---- 3 files changed, 74 insertions(+), 42 deletions(-) diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index 587abae94f..f1dcab3e4f 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -1,5 +1,4 @@ import type { - AnonCredsCredential, AnonCredsCredentialInfo, AnonCredsCredentialRequest, AnonCredsCredentialRequestMetadata, @@ -8,6 +7,7 @@ import type { AnonCredsProofRequestRestriction, AnonCredsRequestedAttributeMatch, AnonCredsRequestedPredicateMatch, + AnonCredsW3CCredential, CreateCredentialRequestOptions, CreateCredentialRequestReturn, CreateLinkSecretOptions, @@ -48,6 +48,8 @@ import { anoncreds, } from '@hyperledger/anoncreds-shared' +import { isString } from 'class-validator' + import { AnonCredsRsModuleConfig } from '../AnonCredsRsModuleConfig' import { AnonCredsRsError } from '../errors/AnonCredsRsError' @@ -91,10 +93,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { credentialRecord = await credentialRepository.getByCredentialId(agentContext, attribute.credentialId) retrievedCredentials.set(attribute.credentialId, credentialRecord) } - - const w3cCred = W3CCredential.fromJson(credentialRecord.credential) - - const revocationRegistryDefinitionId = w3cCred.revocationRegistryId + const revocationRegistryDefinitionId = credentialRecord.credential.credentialStatus?.id const revocationRegistryIndex = credentialRecord.credentialRevocationId // TODO: Check if credential has a revocation registry id (check response from anoncreds-rs API, as it is @@ -283,7 +282,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { await credentialRepository.save( agentContext, new AnonCredsCredentialRecord({ - credential: processedCredential.toJson(), + credential: processedCredential.toJson() as unknown as AnonCredsW3CCredential, credentialId, linkSecretId: linkSecretRecord.linkSecretId, issuerId: options.credentialDefinition.issuerId, @@ -311,20 +310,17 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { .getByCredentialId(agentContext, options.credentialId) const attributes: { [key: string]: string } = {} - const w3cCred = W3CCredential.fromJson(credentialRecord.credential) - - const cred = w3cCred.toLegacy().toJson() as unknown as AnonCredsCredential - - for (const attribute in cred.values) { - attributes[attribute] = cred.values[attribute].raw + const cred = credentialRecord.credential + for (const [key, value] of Object.entries(cred.credentialSubject.attributes)) { + attributes[key] = isString(value) ? value : `${value.predicate} ${value.value}` } return { attributes, - credentialDefinitionId: w3cCred.credentialDefinitionId, + credentialDefinitionId: cred.credentialSchema.definition, credentialId: credentialRecord.credentialId, - schemaId: w3cCred.schemaId, + schemaId: cred.credentialSchema.schema, credentialRevocationId: credentialRecord.credentialRevocationId, - revocationRegistryId: w3cCred.revocationRegistryId, + revocationRegistryId: cred.credentialStatus?.id, methodName: credentialRecord.methodName, } } @@ -346,16 +342,19 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { }) return credentialRecords.map((credentialRecord) => { - const w3cCred = W3CCredential.fromJson(credentialRecord.credential) - - const cred = w3cCred.toLegacy().toJson() as unknown as AnonCredsCredential + const cred = credentialRecord.credential return { - attributes: Object.fromEntries(Object.entries(cred.values).map(([key, value]) => [key, value.raw])), - credentialDefinitionId: w3cCred.credentialDefinitionId, + attributes: Object.fromEntries( + Object.entries(cred.credentialSubject.attributes).map(([key, value]) => [ + key, + isString(value) ? value : `${value.predicate} ${value.value}`, + ]) + ), + credentialDefinitionId: cred.credentialSchema.definition, credentialId: credentialRecord.credentialId, - schemaId: w3cCred.schemaId, + schemaId: cred.credentialSchema.schema, credentialRevocationId: credentialRecord.credentialRevocationId, - revocationRegistryId: w3cCred.revocationRegistryId, + revocationRegistryId: cred.credentialStatus?.id, methodName: credentialRecord.methodName, } }) @@ -411,22 +410,20 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { }) return credentials.map((credentialRecord) => { - const w3cCred = W3CCredential.fromJson(credentialRecord.credential) - const attributes: { [key: string]: string } = {} - const cred = w3cCred.toLegacy().toJson() as unknown as AnonCredsCredential + const cred = credentialRecord.credential - for (const attribute in cred.values) { - attributes[attribute] = cred.values[attribute].raw + for (const [key, value] of Object.entries(cred.credentialSubject.attributes)) { + attributes[key] = isString(value) ? value : `${value.predicate} ${value.value}` } return { credentialInfo: { attributes, - credentialDefinitionId: w3cCred.credentialDefinitionId, + credentialDefinitionId: cred.credentialSchema.definition, credentialId: credentialRecord.credentialId, - schemaId: w3cCred.schemaId, + schemaId: cred.credentialSchema.schema, credentialRevocationId: credentialRecord.credentialRevocationId, - revocationRegistryId: w3cCred.revocationRegistryId, + revocationRegistryId: cred.credentialStatus?.id, methodName: credentialRecord.methodName, }, interval: proofRequest.non_revoked, diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 5213153ff9..840ff55f81 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -1,3 +1,5 @@ +import { SingleOrArray } from '@aries-framework/core/src/utils' + export const anonCredsPredicateType = ['>=', '>', '<=', '<'] as const export type AnonCredsPredicateType = (typeof anonCredsPredicateType)[number] @@ -56,6 +58,42 @@ export interface AnonCredsCredential { signature_correctness_proof: unknown } +export interface AnonCredsW3CCredential { + context: Array + type: Array + issuer: string + issuanceDate?: string + credentialSchema: AnonCredsW3CCredentialSchema + credentialStatus?: AnonCredsW3CCredentialStatus + credentialSubject: AnonCredsW3CCredentialSubject + proof: SingleOrArray +} + +export interface AnonCredsW3CCredentialSchema { + type: string + definition: string + schema: string + encoding: string +} + +export interface AnonCredsW3CCredentialStatus { + type: string + id: string +} +export interface AnonCredsW3CCredentialSubject { + id: string + attributes: Record +} + +export interface AnonCredsW3CCredentialPredicate { + type: string + predicate: string + value: number +} +export interface AnonCredsW3CCredentialProof { + type: string + signature: string +} export interface AnonCredsProof { requested_proof: { revealed_attrs: Record< diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts index a55e795fb6..a910f30aca 100644 --- a/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts +++ b/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts @@ -1,12 +1,13 @@ import type { Tags } from '@aries-framework/core' +import { isString } from 'class-validator' import { BaseRecord, utils } from '@aries-framework/core' -import { JsonObject, W3CCredential } from '@hyperledger/anoncreds-shared' +import { AnonCredsW3CCredential } from '@aries-framework/anoncreds' export interface AnonCredsCredentialRecordProps { id?: string createdAt?: Date - credential: JsonObject + credential: AnonCredsW3CCredential credentialId: string credentialRevocationId?: string linkSecretId: string @@ -48,7 +49,7 @@ export class AnonCredsCredentialRecord extends BaseRecord< public readonly credentialId!: string public readonly credentialRevocationId?: string public readonly linkSecretId!: string - public readonly credential!: JsonObject + public readonly credential!: AnonCredsW3CCredential /** * AnonCreds method name. We don't use names explicitly from the registry (there's no identifier for a registry) @@ -77,23 +78,19 @@ export class AnonCredsCredentialRecord extends BaseRecord< } public getTags() { - const w3cCred = W3CCredential.fromJson(this.credential) const tags: Tags = { ...this._tags, - credentialDefinitionId: w3cCred.credentialDefinitionId, - schemaId: w3cCred.schemaId, + credentialDefinitionId: this.credential.credentialSchema.definition, + schemaId: this.credential.credentialSchema.schema, credentialId: this.credentialId, credentialRevocationId: this.credentialRevocationId, - revocationRegistryId: w3cCred.revocationRegistryId, + revocationRegistryId: this.credential.credentialStatus?.id, linkSecretId: this.linkSecretId, methodName: this.methodName, } - const cred = w3cCred.toLegacy().toJson() - // @ts-ignore - for (const [key, value] of Object.entries(cred.values)) { - // @ts-ignore - tags[`attr::${key}::value`] = value.raw + for (const [key, value] of Object.entries(this.credential.credentialSubject.attributes)) { + tags[`attr::${key}::value`] = isString(value) ? value : `${value.predicate} ${value.value}` tags[`attr::${key}::marker`] = true } From 714ceac485b77a4210c433e61b879e9d800d09b1 Mon Sep 17 00:00:00 2001 From: Abdulbois Date: Wed, 22 Nov 2023 17:17:50 +0500 Subject: [PATCH 3/8] feat: Add AnonCredsW3CPresentation and replace calls with new type --- .../src/services/AnonCredsRsHolderService.ts | 21 +++---- .../AnonCredsCredentialFormatService.ts | 33 +++++------ .../formats/AnonCredsProofFormatService.ts | 56 ++++++++++--------- .../LegacyIndyCredentialFormatService.ts | 4 +- .../formats/LegacyIndyProofFormatService.ts | 6 +- packages/anoncreds/src/models/exchange.ts | 23 ++++---- .../repository/AnonCredsCredentialRecord.ts | 5 +- .../services/AnonCredsHolderServiceOptions.ts | 4 +- .../AnonCredsVerifierServiceOptions.ts | 6 +- .../src/utils/getRevocationRegistries.ts | 9 +-- 10 files changed, 83 insertions(+), 84 deletions(-) diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index f1dcab3e4f..a294d092a1 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -48,8 +48,6 @@ import { anoncreds, } from '@hyperledger/anoncreds-shared' -import { isString } from 'class-validator' - import { AnonCredsRsModuleConfig } from '../AnonCredsRsModuleConfig' import { AnonCredsRsError } from '../errors/AnonCredsRsError' @@ -265,7 +263,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { let credentialObj: W3CCredential | undefined let processedCredential: W3CCredential | undefined try { - credentialObj = credential + credentialObj = W3CCredential.fromJson(credential as unknown as JsonObject) processedCredential = credentialObj.process({ credentialDefinition: credentialDefinition as unknown as JsonObject, credentialRequestMetadata: credentialRequestMetadata as unknown as JsonObject, @@ -277,7 +275,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { const methodName = agentContext.dependencyManager .resolve(AnonCredsRegistryService) - .getRegistryForIdentifier(agentContext, credential.credentialDefinitionId).methodName + .getRegistryForIdentifier(agentContext, credential.credentialSchema.definition).methodName await credentialRepository.save( agentContext, @@ -311,8 +309,8 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { const attributes: { [key: string]: string } = {} const cred = credentialRecord.credential - for (const [key, value] of Object.entries(cred.credentialSubject.attributes)) { - attributes[key] = isString(value) ? value : `${value.predicate} ${value.value}` + for (const [key, value] of Object.entries(cred.credentialSubject)) { + attributes[key] = value } return { attributes, @@ -344,12 +342,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { return credentialRecords.map((credentialRecord) => { const cred = credentialRecord.credential return { - attributes: Object.fromEntries( - Object.entries(cred.credentialSubject.attributes).map(([key, value]) => [ - key, - isString(value) ? value : `${value.predicate} ${value.value}`, - ]) - ), + attributes: Object.fromEntries(Object.entries(cred.credentialSubject).map(([key, value]) => [key, value])), credentialDefinitionId: cred.credentialSchema.definition, credentialId: credentialRecord.credentialId, schemaId: cred.credentialSchema.schema, @@ -413,8 +406,8 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { const attributes: { [key: string]: string } = {} const cred = credentialRecord.credential - for (const [key, value] of Object.entries(cred.credentialSubject.attributes)) { - attributes[key] = isString(value) ? value : `${value.predicate} ${value.value}` + for (const [key, value] of Object.entries(cred.credentialSubject)) { + attributes[key] = value } return { credentialInfo: { diff --git a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts index b262f4d467..fc5c80c5b7 100644 --- a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts @@ -4,6 +4,7 @@ import type { AnonCredsCredentialOffer, AnonCredsCredentialRequest, AnonCredsCredentialRequestMetadata, + AnonCredsW3CCredential, } from '../models' import type { AnonCredsIssuerService, AnonCredsHolderService, GetRevocationRegistryDefinitionReturn } from '../services' import type { AnonCredsCredentialMetadata } from '../utils/metadata' @@ -41,11 +42,6 @@ import { JsonTransformer, } from '@aries-framework/core' -import { - JsonObject, - W3CCredential -} from '@hyperledger/anoncreds-shared' - import { AnonCredsError } from '../error' import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' import { AnonCredsIssuerServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' @@ -363,37 +359,36 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService ) } - const jsonObj: JsonObject = attachment.getDataAsJson() - const anonCredsCredential: W3CCredential = W3CCredential.fromJson(jsonObj) + const anonCredsCredential = attachment.getDataAsJson() const credentialDefinitionResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialDefinitionId) - .getCredentialDefinition(agentContext, anonCredsCredential.credentialDefinitionId) + .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialSchema.definition) + .getCredentialDefinition(agentContext, anonCredsCredential.credentialSchema.definition) if (!credentialDefinitionResult.credentialDefinition) { throw new AriesFrameworkError( - `Unable to resolve credential definition ${anonCredsCredential.credentialDefinitionId}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` + `Unable to resolve credential definition ${anonCredsCredential.credentialSchema.definition}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` ) } const schemaResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialDefinitionId) - .getSchema(agentContext, anonCredsCredential.schemaId) + .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialSchema.definition) + .getSchema(agentContext, anonCredsCredential.credentialSchema.schema) if (!schemaResult.schema) { throw new AriesFrameworkError( - `Unable to resolve schema ${anonCredsCredential.schemaId}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + `Unable to resolve schema ${anonCredsCredential.credentialSchema.schema}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` ) } // Resolve revocation registry if credential is revocable let revocationRegistryResult: null | GetRevocationRegistryDefinitionReturn = null - if (anonCredsCredential.revocationRegistryId) { + if (anonCredsCredential.credentialStatus?.id) { revocationRegistryResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.revocationRegistryId) - .getRevocationRegistryDefinition(agentContext, anonCredsCredential.revocationRegistryId) + .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialStatus?.id) + .getRevocationRegistryDefinition(agentContext, anonCredsCredential.credentialStatus?.id) if (!revocationRegistryResult.revocationRegistryDefinition) { throw new AriesFrameworkError( - `Unable to resolve revocation registry definition ${anonCredsCredential.revocationRegistryId}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` + `Unable to resolve revocation registry definition ${anonCredsCredential.credentialStatus?.id}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` ) } } @@ -419,8 +414,8 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService // If the credential is revocable, store the revocation identifiers in the credential record try { - if (anonCredsCredential.revocationRegistryId) { - const credential = await anonCredsHolderService.getCredential(agentContext, {credentialId}) + if (anonCredsCredential.credentialStatus?.id) { + const credential = await anonCredsHolderService.getCredential(agentContext, { credentialId }) credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { credentialRevocationId: credential.credentialRevocationId, diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts index 001aebb340..268f7eab66 100644 --- a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts @@ -14,6 +14,7 @@ import type { AnonCredsSchema, AnonCredsSelectedCredentials, AnonCredsProofRequest, + AnonCredsW3CPresentation, } from '../models' import type { AnonCredsHolderService, AnonCredsVerifierService, GetCredentialsForProofRequestReturn } from '../services' import type { @@ -205,34 +206,37 @@ export class AnonCredsProofFormatService implements ProofFormatService() - - for (const [referent, attribute] of Object.entries(proofJson.requested_proof.revealed_attrs)) { - if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { - throw new AriesFrameworkError( - `The encoded value for '${referent}' is invalid. ` + - `Expected '${encodeCredentialValue(attribute.raw)}'. ` + - `Actual '${attribute.encoded}'` - ) - } - } - - for (const [, attributeGroup] of Object.entries(proofJson.requested_proof.revealed_attr_groups ?? {})) { - for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { - if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { - throw new AriesFrameworkError( - `The encoded value for '${attributeName}' is invalid. ` + - `Expected '${encodeCredentialValue(attribute.raw)}'. ` + - `Actual '${attribute.encoded}'` - ) - } - } - } - - const schemas = await this.getSchemas(agentContext, new Set(proofJson.identifiers.map((i) => i.schema_id))) + const proofJson = attachment.getDataAsJson() + + // for (const [referent, attribute] of Object.entries(proof.requested_proof.revealed_attrs)) { + // if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + // throw new AriesFrameworkError( + // `The encoded value for '${referent}' is invalid. ` + + // `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + // `Actual '${attribute.encoded}'` + // ) + // } + // } + // for (const [, attributeGroup] of Object.entries(proof.requested_proof.revealed_attr_groups ?? {})) { + // + // for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { + // if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + // throw new AriesFrameworkError( + // `The encoded value for '${attributeName}' is invalid. ` + + // `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + // `Actual '${attribute.encoded}'` + // ) + // } + // } + // } + + const schemas = await this.getSchemas( + agentContext, + new Set(proofJson.verifiableCredential.map((i) => i.credentialSchema.schema)) + ) const credentialDefinitions = await this.getCredentialDefinitions( agentContext, - new Set(proofJson.identifiers.map((i) => i.cred_def_id)) + new Set(proofJson.verifiableCredential.map((i) => i.credentialSchema.definition)) ) const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) diff --git a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts index 7434662aed..6f724ff3d4 100644 --- a/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyCredentialFormatService.ts @@ -3,7 +3,7 @@ import type { AnonCredsCredential, AnonCredsCredentialOffer, AnonCredsCredentialRequest, - AnonCredsCredentialRequestMetadata, + AnonCredsCredentialRequestMetadata, AnonCredsW3CCredential, } from '../models' import type { AnonCredsIssuerService, AnonCredsHolderService, GetRevocationRegistryDefinitionReturn } from '../services' import type { AnonCredsCredentialMetadata } from '../utils/metadata' @@ -419,7 +419,7 @@ export class LegacyIndyCredentialFormatService implements CredentialFormatServic const credentialId = await anonCredsHolderService.storeCredential(agentContext, { credentialId: utils.uuid(), credentialRequestMetadata, - credential: W3CCredential.fromJson(anonCredsCredential as unknown as JsonObject), + credential: anonCredsCredential as unknown as AnonCredsW3CCredential, credentialDefinitionId: credentialDefinitionResult.credentialDefinitionId, credentialDefinition: credentialDefinitionResult.credentialDefinition, schema: schemaResult.schema, diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts index 960b596e74..d18efa3be1 100644 --- a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -59,6 +59,7 @@ import { getRevocationRegistriesForProof, } from '../utils' import { isUnqualifiedCredentialDefinitionId, isUnqualifiedSchemaId } from '../utils/indyIdentifiers' +import {AnonCredsW3CPresentation} from "../models"; const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' @@ -240,11 +241,12 @@ export class LegacyIndyProofFormatService implements ProofFormatService i.cred_def_id)) ) - const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) + const w3cPresentation = proofJson as unknown as AnonCredsW3CPresentation + const revocationRegistries = await getRevocationRegistriesForProof(agentContext, w3cPresentation) return await verifierService.verifyProof(agentContext, { proofRequest: proofRequestJson, - proof: proofJson, + proof: w3cPresentation, schemas, credentialDefinitions, revocationRegistries, diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 840ff55f81..398255cb0e 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -65,7 +65,7 @@ export interface AnonCredsW3CCredential { issuanceDate?: string credentialSchema: AnonCredsW3CCredentialSchema credentialStatus?: AnonCredsW3CCredentialStatus - credentialSubject: AnonCredsW3CCredentialSubject + credentialSubject: Record proof: SingleOrArray } @@ -80,19 +80,22 @@ export interface AnonCredsW3CCredentialStatus { type: string id: string } -export interface AnonCredsW3CCredentialSubject { - id: string - attributes: Record +export interface AnonCredsW3CCredentialProof { + type: string + signature: string } -export interface AnonCredsW3CCredentialPredicate { - type: string - predicate: string - value: number +export interface AnonCredsW3CPresentation { + context: Array + type: Array + verifiableCredential: Array + proof: AnonCredsW3CPresentationProof } -export interface AnonCredsW3CCredentialProof { + +export interface AnonCredsW3CPresentationProof { type: string - signature: string + challenge: string + proofValue: string } export interface AnonCredsProof { requested_proof: { diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts index a910f30aca..74817bbfc4 100644 --- a/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts +++ b/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts @@ -1,6 +1,5 @@ import type { Tags } from '@aries-framework/core' -import { isString } from 'class-validator' import { BaseRecord, utils } from '@aries-framework/core' import { AnonCredsW3CCredential } from '@aries-framework/anoncreds' @@ -89,8 +88,8 @@ export class AnonCredsCredentialRecord extends BaseRecord< methodName: this.methodName, } - for (const [key, value] of Object.entries(this.credential.credentialSubject.attributes)) { - tags[`attr::${key}::value`] = isString(value) ? value : `${value.predicate} ${value.value}` + for (const [key, value] of Object.entries(this.credential.credentialSubject)) { + tags[`attr::${key}::value`] = value tags[`attr::${key}::marker`] = true } diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index cc99b2ef53..568d17c0c0 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -2,9 +2,9 @@ import type { AnonCredsCredentialInfo, AnonCredsCredentialRequestMetadata, AnonCredsSelectedCredentials, + AnonCredsW3CCredential, } from '../models' import type { - AnonCredsCredential, AnonCredsCredentialOffer, AnonCredsCredentialRequest, AnonCredsProofRequest, @@ -47,7 +47,7 @@ export interface CreateProofOptions { export interface StoreCredentialOptions { credentialRequestMetadata: AnonCredsCredentialRequestMetadata - credential: W3CCredential + credential: AnonCredsW3CCredential credentialDefinition: AnonCredsCredentialDefinition schema: AnonCredsSchema credentialDefinitionId: string diff --git a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts index 1bdd959f15..936badad1b 100644 --- a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts @@ -1,4 +1,4 @@ -import type { AnonCredsProof, AnonCredsProofRequest } from '../models/exchange' +import type { AnonCredsProofRequest } from '../models/exchange' import type { AnonCredsCredentialDefinition, AnonCredsRevocationStatusList, @@ -6,9 +6,11 @@ import type { AnonCredsSchema, } from '../models/registry' +import { AnonCredsW3CPresentation } from '../models/exchange' + export interface VerifyProofOptions { proofRequest: AnonCredsProofRequest - proof: AnonCredsProof + proof: AnonCredsW3CPresentation schemas: { [schemaId: string]: AnonCredsSchema } diff --git a/packages/anoncreds/src/utils/getRevocationRegistries.ts b/packages/anoncreds/src/utils/getRevocationRegistries.ts index 0141bf257b..b16556694c 100644 --- a/packages/anoncreds/src/utils/getRevocationRegistries.ts +++ b/packages/anoncreds/src/utils/getRevocationRegistries.ts @@ -8,6 +8,7 @@ import { AnonCredsRegistryService } from '../services' import { assertBestPracticeRevocationInterval } from './revocationInterval' import { downloadTailsFile } from './tails' +import { AnonCredsW3CPresentation } from "../models"; export async function getRevocationRegistriesForRequest( agentContext: AgentContext, @@ -156,13 +157,13 @@ export async function getRevocationRegistriesForRequest( } } -export async function getRevocationRegistriesForProof(agentContext: AgentContext, proof: AnonCredsProof) { +export async function getRevocationRegistriesForProof(agentContext: AgentContext, proof: AnonCredsW3CPresentation) { const revocationRegistries: VerifyProofOptions['revocationRegistries'] = {} const revocationRegistryPromises = [] - for (const identifier of proof.identifiers) { - const revocationRegistryId = identifier.rev_reg_id - const timestamp = identifier.timestamp + for (const identifier of proof.verifiableCredential) { + const revocationRegistryId = identifier.credentialStatus?.id + const timestamp = 0 // Skip if no revocation registry id is present if (!revocationRegistryId || !timestamp) continue From 17b080aef095396352df8d5043a8861e1416e05e Mon Sep 17 00:00:00 2001 From: Abdulbois Date: Thu, 23 Nov 2023 17:25:51 +0500 Subject: [PATCH 4/8] feat: Enable saving legacy and W3C formant, enable to present proof in both legacy and W3C formats --- demo/src/Faber.ts | 3 +- .../src/services/AnonCredsRsHolderService.ts | 246 ++++++++++++------ .../services/AnonCredsRsVerifierService.ts | 11 +- .../src/formats/AnonCredsProofFormat.ts | 1 + .../formats/AnonCredsProofFormatService.ts | 112 ++++---- .../formats/LegacyIndyProofFormatService.ts | 20 +- packages/anoncreds/src/models/exchange.ts | 1 + .../repository/AnonCredsCredentialRecord.ts | 16 +- .../AnonCredsW3CCredentialRecord.ts | 98 +++++++ .../AnonCredsW3CCredentialRepository.ts | 31 +++ packages/anoncreds/src/repository/index.ts | 4 +- .../services/AnonCredsHolderServiceOptions.ts | 2 + .../AnonCredsVerifierServiceOptions.ts | 4 +- .../src/utils/getRevocationRegistries.ts | 58 ++++- packages/core/src/modules/proofs/ProofsApi.ts | 1 + .../src/modules/proofs/ProofsApiOptions.ts | 1 + .../formats/ProofFormatServiceOptions.ts | 1 + .../proofs/protocol/ProofProtocolOptions.ts | 1 + .../protocol/v2/ProofFormatCoordinator.ts | 3 + .../proofs/protocol/v2/V2ProofProtocol.ts | 12 +- .../src/IndySdkToAskarMigrationUpdater.ts | 4 +- 21 files changed, 476 insertions(+), 154 deletions(-) create mode 100644 packages/anoncreds/src/repository/AnonCredsW3CCredentialRecord.ts create mode 100644 packages/anoncreds/src/repository/AnonCredsW3CCredentialRepository.ts diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index 91befb8918..c66ff61189 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -29,7 +29,7 @@ export class Faber extends BaseAgent { } public static async build(): Promise { - const faber = new Faber(9001, 'faber') + const faber = new Faber(9001, 'faber11') await faber.initializeAgent() return faber } @@ -266,6 +266,7 @@ export class Faber extends BaseAgent { requested_attributes: proofAttribute, }, }, + isW3C: true, }) this.ui.updateBottomBar( `\nProof request sent!\n\nGo to the Alice agent to accept the proof request\n\n${Color.Reset}` diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index a294d092a1..29cf412d57 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -1,4 +1,5 @@ import type { + AnonCredsCredential, AnonCredsCredentialInfo, AnonCredsCredentialRequest, AnonCredsCredentialRequestMetadata, @@ -19,37 +20,41 @@ import type { GetCredentialsOptions, StoreCredentialOptions, } from '@aries-framework/anoncreds' +import { + AnonCredsLinkSecretRepository, + AnonCredsRegistryService, + AnonCredsRestrictionWrapper, + AnonCredsW3CCredentialRecord, + AnonCredsW3CCredentialRepository, + storeLinkSecret, + unqualifiedCredentialDefinitionIdRegex, +} from '@aries-framework/anoncreds' import type { AgentContext, Query, SimpleQuery } from '@aries-framework/core' +import { AriesFrameworkError, injectable, JsonTransformer, TypedArrayEncoder, utils} from '@aries-framework/core' import type { CredentialProve, CredentialRequestMetadata, JsonObject, W3CCredentialEntry, } from '@hyperledger/anoncreds-shared' - -import { - AnonCredsCredentialRecord, - AnonCredsCredentialRepository, - AnonCredsLinkSecretRepository, - AnonCredsRestrictionWrapper, - unqualifiedCredentialDefinitionIdRegex, - AnonCredsRegistryService, - storeLinkSecret, -} from '@aries-framework/anoncreds' -import { AriesFrameworkError, JsonTransformer, TypedArrayEncoder, injectable, utils } from '@aries-framework/core' import { - W3CCredential, - W3CCredentialRequest, + anoncreds, + Credential, CredentialRevocationState, LinkSecret, - W3CPresentation, + Presentation, RevocationRegistryDefinition, RevocationStatusList, - anoncreds, + W3CCredential, + W3CCredentialRequest, + W3CPresentation, } from '@hyperledger/anoncreds-shared' +import { AnonCredsCredentialRecord } from '@aries-framework/anoncreds/src/repository/AnonCredsCredentialRecord' +import { AnonCredsCredentialRepository } from '@aries-framework/anoncreds/src/repository/AnonCredsCredentialRepository' import { AnonCredsRsModuleConfig } from '../AnonCredsRsModuleConfig' import { AnonCredsRsError } from '../errors/AnonCredsRsError' +import { CredentialEntry } from "@hyperledger/anoncreds-nodejs"; @injectable() export class AnonCredsRsHolderService implements AnonCredsHolderService { @@ -66,7 +71,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options - let presentation: W3CPresentation | undefined + let presentation: W3CPresentation | Presentation | undefined try { const rsCredentialDefinitions: Record = {} for (const credDefId in credentialDefinitions) { @@ -77,21 +82,28 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { for (const schemaId in schemas) { rsSchemas[schemaId] = schemas[schemaId] as unknown as JsonObject } + let credentialRepository: AnonCredsW3CCredentialRepository | AnonCredsCredentialRepository - const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + if (proofRequest.isW3C) { + credentialRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) + } else { + credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + } // Cache retrieved credentials in order to minimize storage calls - const retrievedCredentials = new Map() + const retrievedCredentials = new Map() const credentialEntryFromAttribute = async ( attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch - ): Promise<{ linkSecretId: string; credentialEntry: W3CCredentialEntry }> => { + ): Promise<{ linkSecretId: string; credentialEntry: W3CCredentialEntry | CredentialEntry }> => { let credentialRecord = retrievedCredentials.get(attribute.credentialId) if (!credentialRecord) { credentialRecord = await credentialRepository.getByCredentialId(agentContext, attribute.credentialId) retrievedCredentials.set(attribute.credentialId, credentialRecord) } - const revocationRegistryDefinitionId = credentialRecord.credential.credentialStatus?.id + + // @ts-ignore + const revocationRegistryDefinitionId = credentialRecord.credential.credentialStatus?.id || credentialRecord?.credential?.rev_reg_id const revocationRegistryIndex = credentialRecord.credentialRevocationId // TODO: Check if credential has a revocation registry id (check response from anoncreds-rs API, as it is @@ -140,7 +152,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } const credentialsProve: CredentialProve[] = [] - const credentials: { linkSecretId: string; credentialEntry: W3CCredentialEntry }[] = [] + const credentials: { linkSecretId: string; credentialEntry: W3CCredentialEntry | CredentialEntry }[] = [] let entryIndex = 0 for (const referent in selectedCredentials.attributes) { @@ -171,15 +183,27 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { throw new AnonCredsRsError('Link Secret value not stored') } - presentation = W3CPresentation.create({ - credentialDefinitions: rsCredentialDefinitions, - schemas: rsSchemas, - presentationRequest: proofRequest as unknown as JsonObject, - credentials: credentials.map((entry) => entry.credentialEntry), - credentialsProve, - // selfAttest: selectedCredentials.selfAttestedAttributes, - linkSecret: linkSecretRecord.value, - }) + if (proofRequest.isW3C) { + presentation = W3CPresentation.create({ + credentialDefinitions: rsCredentialDefinitions, + schemas: rsSchemas, + presentationRequest: proofRequest as unknown as JsonObject, + credentials: credentials.map((entry) => entry.credentialEntry as W3CCredentialEntry), + credentialsProve, + // selfAttest: selectedCredentials.selfAttestedAttributes, + linkSecret: linkSecretRecord.value, + }) + } else { + presentation = Presentation.create({ + credentialDefinitions: rsCredentialDefinitions, + schemas: rsSchemas, + presentationRequest: proofRequest as unknown as JsonObject, + credentials: credentials.map((entry) => entry.credentialEntry as CredentialEntry), + credentialsProve, + selfAttest: selectedCredentials.selfAttestedAttributes, + linkSecret: linkSecretRecord.value, + }) + } return presentation.toJson() as unknown as AnonCredsProof } finally { @@ -260,8 +284,8 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { const credentialId = options.credentialId ?? utils.uuid() - let credentialObj: W3CCredential | undefined - let processedCredential: W3CCredential | undefined + let credentialObj: W3CCredential | Credential | undefined + let processedCredential: W3CCredential | Credential | undefined try { credentialObj = W3CCredential.fromJson(credential as unknown as JsonObject) processedCredential = credentialObj.process({ @@ -271,15 +295,15 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { revocationRegistryDefinition, }) - const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + const credentialW3CRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) const methodName = agentContext.dependencyManager .resolve(AnonCredsRegistryService) .getRegistryForIdentifier(agentContext, credential.credentialSchema.definition).methodName - await credentialRepository.save( + await credentialW3CRepository.save( agentContext, - new AnonCredsCredentialRecord({ + new AnonCredsW3CCredentialRecord({ credential: processedCredential.toJson() as unknown as AnonCredsW3CCredential, credentialId, linkSecretId: linkSecretRecord.linkSecretId, @@ -292,6 +316,31 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { }) ) + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + + // credentialObj = Credential.fromW3C({ credential: credentialObj }) + // credentialObj = Credential.fromJson(credentialObj.toLegacy().toJson()) + // processedCredential = credentialObj.process({ + // credentialDefinition: credentialDefinition as unknown as JsonObject, + // credentialRequestMetadata: credentialRequestMetadata as unknown as JsonObject, + // linkSecret: linkSecretRecord.value, + // revocationRegistryDefinition, + // }) + await credentialRepository.save( + agentContext, + new AnonCredsCredentialRecord({ + credential: processedCredential.toLegacy().toJson() as unknown as AnonCredsCredential, + credentialId: options.credentialId ?? utils.uuid(), + linkSecretId: linkSecretRecord.linkSecretId, + issuerId: options.credentialDefinition.issuerId, + schemaName: schema.name, + schemaIssuerId: schema.issuerId, + schemaVersion: schema.version, + credentialRevocationId: processedCredential.revocationRegistryIndex?.toString(), + methodName, + }) + ) + return credentialId } finally { credentialObj?.handle.clear() @@ -303,23 +352,71 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { agentContext: AgentContext, options: GetCredentialOptions ): Promise { - const credentialRecord = await agentContext.dependencyManager - .resolve(AnonCredsCredentialRepository) - .getByCredentialId(agentContext, options.credentialId) + let credInfo: AnonCredsCredentialInfo - const attributes: { [key: string]: string } = {} + if (options.isLegacy) { + const credentialRecord = await agentContext.dependencyManager + .resolve(AnonCredsCredentialRepository) + .getByCredentialId(agentContext, options.credentialId) + + credInfo = this.createAnonCredsCredentialInfo(credentialRecord) + } else { + const credentialRecord = await agentContext.dependencyManager + .resolve(AnonCredsW3CCredentialRepository) + .getByCredentialId(agentContext, options.credentialId) + + credInfo = this.createAnonCredsCredentialInfoW3C(credentialRecord) + } + + return credInfo + } + private createAnonCredsCredentialInfoW3C(credentialRecord: AnonCredsW3CCredentialRecord): AnonCredsCredentialInfo { const cred = credentialRecord.credential - for (const [key, value] of Object.entries(cred.credentialSubject)) { + const credId = credentialRecord.credentialId + const methodName = credentialRecord.methodName + const credDef = cred.credentialSchema.definition + const schema = cred.credentialSchema.schema + const revRegId = cred.credentialStatus?.id + const revId = credentialRecord.credentialRevocationId + const attributes: { [key: string]: string } = {} + + for (const [key, value] of Object.entries(credentialRecord.credential.credentialSubject)) { attributes[key] = value } return { attributes, - credentialDefinitionId: cred.credentialSchema.definition, - credentialId: credentialRecord.credentialId, - schemaId: cred.credentialSchema.schema, - credentialRevocationId: credentialRecord.credentialRevocationId, - revocationRegistryId: cred.credentialStatus?.id, - methodName: credentialRecord.methodName, + credentialDefinitionId: credDef, + credentialId: credId, + schemaId: schema, + credentialRevocationId: revId, + revocationRegistryId: revRegId, + methodName: methodName, + } + } + private createAnonCredsCredentialInfo(credentialRecord: AnonCredsCredentialRecord): AnonCredsCredentialInfo { + const credId = credentialRecord.credentialId + const methodName = credentialRecord.methodName + const credDef = credentialRecord.credential.cred_def_id + const schema = credentialRecord.credential.schema_id + const revId = credentialRecord.credentialRevocationId + const revRegId = credentialRecord.credential.rev_reg_id + const attributes: { [key: string]: string } = {} + + for (const attribute in credentialRecord.credential.values) { + attributes[attribute] = credentialRecord.credential.values[attribute].raw + } + + for (const attribute in credentialRecord.credential.values) { + attributes[attribute] = credentialRecord.credential.values[attribute].raw + } + return { + attributes, + credentialDefinitionId: credDef, + credentialId: credId, + schemaId: schema, + credentialRevocationId: revId, + revocationRegistryId: revRegId, + methodName: methodName, } } @@ -328,7 +425,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { options: GetCredentialsOptions ): Promise { const credentialRecords = await agentContext.dependencyManager - .resolve(AnonCredsCredentialRepository) + .resolve(AnonCredsW3CCredentialRepository) .findByQuery(agentContext, { credentialDefinitionId: options.credentialDefinitionId, schemaId: options.schemaId, @@ -354,7 +451,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } public async deleteCredential(agentContext: AgentContext, credentialId: string): Promise { - const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) const credentialRecord = await credentialRepository.getByCredentialId(agentContext, credentialId) await credentialRepository.delete(agentContext, credentialRecord) } @@ -377,7 +474,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { // Make sure the attribute(s) that are requested are present using the marker tag const attributes = requestedAttribute.names ?? [requestedAttribute.name] - const attributeQuery: SimpleQuery = {} + const attributeQuery: SimpleQuery = {} for (const attribute of attributes) { attributeQuery[`attr::${attribute}::marker`] = true } @@ -396,41 +493,42 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { $and.push(options.extraQuery) } - const credentials = await agentContext.dependencyManager - .resolve(AnonCredsCredentialRepository) - .findByQuery(agentContext, { - $and, + if (proofRequest?.isW3C) { + let credentials = await agentContext.dependencyManager + .resolve(AnonCredsW3CCredentialRepository) + .findByQuery(agentContext, { $and }) + + credentials = credentials.filter((value) => value.type == AnonCredsW3CCredentialRecord.type) + + return credentials.map((credentialRecord) => { + return { + credentialInfo: this.createAnonCredsCredentialInfoW3C(credentialRecord), + interval: proofRequest.non_revoked, + } }) + } else { + let credentials = await agentContext.dependencyManager + .resolve(AnonCredsCredentialRepository) + .findByQuery(agentContext, { $and }) - return credentials.map((credentialRecord) => { - const attributes: { [key: string]: string } = {} - const cred = credentialRecord.credential + credentials = credentials.filter((value) => value.type == AnonCredsCredentialRecord.type) - for (const [key, value] of Object.entries(cred.credentialSubject)) { - attributes[key] = value - } - return { - credentialInfo: { - attributes, - credentialDefinitionId: cred.credentialSchema.definition, - credentialId: credentialRecord.credentialId, - schemaId: cred.credentialSchema.schema, - credentialRevocationId: credentialRecord.credentialRevocationId, - revocationRegistryId: cred.credentialStatus?.id, - methodName: credentialRecord.methodName, - }, - interval: proofRequest.non_revoked, - } - }) + return credentials.map((credentialRecord) => { + return { + credentialInfo: this.createAnonCredsCredentialInfo(credentialRecord), + interval: proofRequest.non_revoked, + } + }) + } } private queryFromRestrictions(restrictions: AnonCredsProofRequestRestriction[]) { - const query: Query[] = [] + const query: Query[] = [] const { restrictions: parsedRestrictions } = JsonTransformer.fromJSON({ restrictions }, AnonCredsRestrictionWrapper) for (const restriction of parsedRestrictions) { - const queryElements: SimpleQuery = {} + const queryElements: SimpleQuery = {} if (restriction.credentialDefinitionId) { queryElements.credentialDefinitionId = restriction.credentialDefinitionId diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts index 73e662bb29..0c88b51c2a 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts @@ -3,17 +3,20 @@ import type { AgentContext } from '@aries-framework/core' import type { JsonObject } from '@hyperledger/anoncreds-shared' import { injectable } from '@aries-framework/core' -import { W3CPresentation } from '@hyperledger/anoncreds-shared' +import { Presentation, W3CPresentation } from '@hyperledger/anoncreds-shared' @injectable() export class AnonCredsRsVerifierService implements AnonCredsVerifierService { public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { const { credentialDefinitions, proof, proofRequest, revocationRegistries, schemas } = options - let presentation: W3CPresentation | undefined + let presentation: W3CPresentation | Presentation | undefined try { - presentation = W3CPresentation.fromJson(proof as unknown as JsonObject) - + if (proofRequest.isW3C) { + presentation = W3CPresentation.fromJson(proof as unknown as JsonObject) + } else { + presentation = Presentation.fromJson(proof as unknown as JsonObject) + } const rsCredentialDefinitions: Record = {} for (const credDefId in credentialDefinitions) { rsCredentialDefinitions[credDefId] = credentialDefinitions[credDefId] as unknown as JsonObject diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormat.ts b/packages/anoncreds/src/formats/AnonCredsProofFormat.ts index 0c326943f8..cff20455ae 100644 --- a/packages/anoncreds/src/formats/AnonCredsProofFormat.ts +++ b/packages/anoncreds/src/formats/AnonCredsProofFormat.ts @@ -57,6 +57,7 @@ export interface AnonCredsCredentialsForProofRequest { export interface AnonCredsGetCredentialsForProofRequestOptions { filterByNonRevocationRequirements?: boolean + isW3C?: boolean } export interface AnonCredsProofFormat extends ProofFormat { diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts index 268f7eab66..0622e173de 100644 --- a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts @@ -59,6 +59,7 @@ import { getRevocationRegistriesForRequest, getRevocationRegistriesForProof, } from '../utils' +import { getRevocationRegistriesForProofW3C } from '../utils/getRevocationRegistries' const ANONCREDS_PRESENTATION_PROPOSAL = 'anoncreds/proof-request@v1.0' const ANONCREDS_PRESENTATION_REQUEST = 'anoncreds/proof-request@v1.0' @@ -127,7 +128,7 @@ export class AnonCredsProofFormatService implements ProofFormatService + { attachmentId, proofFormats, isW3C }: FormatCreateRequestOptions ): Promise { const format = new ProofFormatSpec({ format: ANONCREDS_PRESENTATION_REQUEST, @@ -146,6 +147,7 @@ export class AnonCredsProofFormatService implements ProofFormatService() - const anoncredsFormat = proofFormats?.anoncreds const selectedCredentials = @@ -201,53 +202,68 @@ export class AnonCredsProofFormatService implements ProofFormatService(AnonCredsVerifierServiceSymbol) const proofRequestJson = requestAttachment.getDataAsJson() + if (proofRequestJson.isW3C) { + const proofJson = attachment.getDataAsJson() - // NOTE: we don't do validation here, as this is handled by the AnonCreds implementation, however - // this can lead to confusing error messages. We should consider doing validation here as well. - // Defining a class-transformer/class-validator class seems a bit overkill, and the usage of interfaces - // for the anoncreds package keeps things simple. Maybe we can try to use something like zod to validate - const proofJson = attachment.getDataAsJson() - - // for (const [referent, attribute] of Object.entries(proof.requested_proof.revealed_attrs)) { - // if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { - // throw new AriesFrameworkError( - // `The encoded value for '${referent}' is invalid. ` + - // `Expected '${encodeCredentialValue(attribute.raw)}'. ` + - // `Actual '${attribute.encoded}'` - // ) - // } - // } - // for (const [, attributeGroup] of Object.entries(proof.requested_proof.revealed_attr_groups ?? {})) { - // - // for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { - // if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { - // throw new AriesFrameworkError( - // `The encoded value for '${attributeName}' is invalid. ` + - // `Expected '${encodeCredentialValue(attribute.raw)}'. ` + - // `Actual '${attribute.encoded}'` - // ) - // } - // } - // } - - const schemas = await this.getSchemas( - agentContext, - new Set(proofJson.verifiableCredential.map((i) => i.credentialSchema.schema)) - ) - const credentialDefinitions = await this.getCredentialDefinitions( - agentContext, - new Set(proofJson.verifiableCredential.map((i) => i.credentialSchema.definition)) - ) + const schemas = await this.getSchemas( + agentContext, + new Set(proofJson.verifiableCredential.map((i) => i.credentialSchema.schema)) + ) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(proofJson.verifiableCredential.map((i) => i.credentialSchema.definition)) + ) - const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) + const revocationRegistries = await getRevocationRegistriesForProofW3C(agentContext, proofJson) + + return await verifierService.verifyProof(agentContext, { + proofRequest: proofRequestJson, + proof: proofJson, + schemas, + credentialDefinitions, + revocationRegistries, + }) + } else { + const proofJson = attachment.getDataAsJson() + + // for (const [referent, attribute] of Object.entries(proofJson.requested_proof.revealed_attrs)) { + // if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + // throw new AriesFrameworkError( + // `The encoded value for '${referent}' is invalid. ` + + // `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + // `Actual '${attribute.encoded}'` + // ) + // } + // } + // + // for (const [, attributeGroup] of Object.entries(proofJson.requested_proof.revealed_attr_groups ?? {})) { + // for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { + // if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + // throw new AriesFrameworkError( + // `The encoded value for '${attributeName}' is invalid. ` + + // `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + // `Actual '${attribute.encoded}'` + // ) + // } + // } + // } + + const schemas = await this.getSchemas(agentContext, new Set(proofJson.identifiers.map((i) => i.schema_id))) + const credentialDefinitions = await this.getCredentialDefinitions( + agentContext, + new Set(proofJson.identifiers.map((i) => i.cred_def_id)) + ) - return await verifierService.verifyProof(agentContext, { - proofRequest: proofRequestJson, - proof: proofJson, - schemas, - credentialDefinitions, - revocationRegistries, - }) + const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) + + return await verifierService.verifyProof(agentContext, { + proofRequest: proofRequestJson, + proof: proofJson, + schemas, + credentialDefinitions, + revocationRegistries, + }) + } } public async getCredentialsForRequest( @@ -585,7 +601,9 @@ export class AnonCredsProofFormatService implements ProofFormatService c.credentialInfo ?? holderService.getCredential(agentContext, { credentialId: c.credentialId }) + async (c) => + c.credentialInfo ?? + holderService.getCredential(agentContext, { credentialId: c.credentialId, isLegacy: !proofRequest.isW3C }) ) ) diff --git a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts index d18efa3be1..6b0eb51947 100644 --- a/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts +++ b/packages/anoncreds/src/formats/LegacyIndyProofFormatService.ts @@ -45,7 +45,7 @@ import { } from '@aries-framework/core' import { AnonCredsProofRequest as AnonCredsProofRequestClass } from '../models/AnonCredsProofRequest' -import { AnonCredsVerifierServiceSymbol, AnonCredsHolderServiceSymbol } from '../services' +import { AnonCredsVerifierServiceSymbol, AnonCredsHolderServiceSymbol, VerifyProofOptions} from '../services' import { AnonCredsRegistryService } from '../services/registry/AnonCredsRegistryService' import { sortRequestedCredentialsMatches, @@ -59,7 +59,8 @@ import { getRevocationRegistriesForProof, } from '../utils' import { isUnqualifiedCredentialDefinitionId, isUnqualifiedSchemaId } from '../utils/indyIdentifiers' -import {AnonCredsW3CPresentation} from "../models"; +import { AnonCredsW3CPresentation } from '../models' +import { getRevocationRegistriesForProofW3C } from '../utils/getRevocationRegistries' const V2_INDY_PRESENTATION_PROPOSAL = 'hlindy/proof-req@v2.0' const V2_INDY_PRESENTATION_REQUEST = 'hlindy/proof-req@v2.0' @@ -240,13 +241,18 @@ export class LegacyIndyProofFormatService implements ProofFormatService i.cred_def_id)) ) - - const w3cPresentation = proofJson as unknown as AnonCredsW3CPresentation - const revocationRegistries = await getRevocationRegistriesForProof(agentContext, w3cPresentation) - + let presentation: AnonCredsW3CPresentation | AnonCredsProof + let revocationRegistries: VerifyProofOptions['revocationRegistries'] + if (proofRequestJson.isW3C) { + presentation = proofJson as unknown as AnonCredsW3CPresentation + revocationRegistries = await getRevocationRegistriesForProofW3C(agentContext, presentation) + } else { + presentation = proofJson as unknown as AnonCredsProof + revocationRegistries = await getRevocationRegistriesForProof(agentContext, presentation) + } return await verifierService.verifyProof(agentContext, { proofRequest: proofRequestJson, - proof: w3cPresentation, + proof: presentation, schemas, credentialDefinitions, revocationRegistries, diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 398255cb0e..584fbd27d6 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -163,4 +163,5 @@ export interface AnonCredsProofRequest { requested_predicates: Record non_revoked?: AnonCredsNonRevokedInterval ver?: '1.0' | '2.0' + isW3C?: boolean } diff --git a/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts b/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts index 74817bbfc4..d9627b6fb3 100644 --- a/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts +++ b/packages/anoncreds/src/repository/AnonCredsCredentialRecord.ts @@ -1,12 +1,12 @@ +import type { AnonCredsCredential } from '../models' import type { Tags } from '@aries-framework/core' import { BaseRecord, utils } from '@aries-framework/core' -import { AnonCredsW3CCredential } from '@aries-framework/anoncreds' export interface AnonCredsCredentialRecordProps { id?: string createdAt?: Date - credential: AnonCredsW3CCredential + credential: AnonCredsCredential credentialId: string credentialRevocationId?: string linkSecretId: string @@ -48,7 +48,7 @@ export class AnonCredsCredentialRecord extends BaseRecord< public readonly credentialId!: string public readonly credentialRevocationId?: string public readonly linkSecretId!: string - public readonly credential!: AnonCredsW3CCredential + public readonly credential!: AnonCredsCredential /** * AnonCreds method name. We don't use names explicitly from the registry (there's no identifier for a registry) @@ -79,17 +79,17 @@ export class AnonCredsCredentialRecord extends BaseRecord< public getTags() { const tags: Tags = { ...this._tags, - credentialDefinitionId: this.credential.credentialSchema.definition, - schemaId: this.credential.credentialSchema.schema, + credentialDefinitionId: this.credential.cred_def_id, + schemaId: this.credential.schema_id, credentialId: this.credentialId, credentialRevocationId: this.credentialRevocationId, - revocationRegistryId: this.credential.credentialStatus?.id, + revocationRegistryId: this.credential.rev_reg_id, linkSecretId: this.linkSecretId, methodName: this.methodName, } - for (const [key, value] of Object.entries(this.credential.credentialSubject)) { - tags[`attr::${key}::value`] = value + for (const [key, value] of Object.entries(this.credential.values)) { + tags[`attr::${key}::value`] = value.raw tags[`attr::${key}::marker`] = true } diff --git a/packages/anoncreds/src/repository/AnonCredsW3CCredentialRecord.ts b/packages/anoncreds/src/repository/AnonCredsW3CCredentialRecord.ts new file mode 100644 index 0000000000..0a78c032ac --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsW3CCredentialRecord.ts @@ -0,0 +1,98 @@ +import type { Tags } from '@aries-framework/core' + +import { BaseRecord, utils } from '@aries-framework/core' +import { AnonCredsW3CCredential } from '@aries-framework/anoncreds' + +export interface AnonCredsW3CCredentialRecordProps { + id?: string + createdAt?: Date + credential: AnonCredsW3CCredential + credentialId: string + credentialRevocationId?: string + linkSecretId: string + schemaName: string + schemaVersion: string + schemaIssuerId: string + issuerId: string + methodName: string +} + +export type DefaultAnonCredsW3CCredentialTags = { + credentialId: string + linkSecretId: string + credentialDefinitionId: string + credentialRevocationId?: string + revocationRegistryId?: string + schemaId: string + methodName: string + + // the following keys can be used for every `attribute name` in credential. + [key: `attr::${string}::marker`]: true | undefined + [key: `attr::${string}::value`]: string | undefined +} + +export type CustomAnonCredsW3CCredentialTags = { + schemaName: string + schemaVersion: string + schemaIssuerId: string + issuerId: string +} + +export class AnonCredsW3CCredentialRecord extends BaseRecord< + DefaultAnonCredsW3CCredentialTags, + CustomAnonCredsW3CCredentialTags +> { + public static readonly type = 'AnonCredsW3CCredentialRecord' + public readonly type = AnonCredsW3CCredentialRecord.type + + public readonly credentialId!: string + public readonly credentialRevocationId?: string + public readonly linkSecretId!: string + public readonly credential!: AnonCredsW3CCredential + + /** + * AnonCreds method name. We don't use names explicitly from the registry (there's no identifier for a registry) + * @see https://hyperledger.github.io/anoncreds-methods-registry/ + */ + public readonly methodName!: string + + public constructor(props: AnonCredsW3CCredentialRecordProps) { + super() + + if (props) { + this.id = props.id ?? utils.uuid() + this.createdAt = props.createdAt ?? new Date() + this.credentialId = props.credentialId + this.credential = props.credential + this.credentialRevocationId = props.credentialRevocationId + this.linkSecretId = props.linkSecretId + this.methodName = props.methodName + this.setTags({ + issuerId: props.issuerId, + schemaIssuerId: props.schemaIssuerId, + schemaName: props.schemaName, + schemaVersion: props.schemaVersion, + }) + } + } + + public getTags() { + const tags: Tags = { + ...this._tags, + credentialDefinitionId: this.credential.credentialSchema.definition, + schemaId: this.credential.credentialSchema.schema, + credentialId: this.credentialId, + credentialRevocationId: this.credentialRevocationId, + revocationRegistryId: this.credential.credentialStatus?.id, + linkSecretId: this.linkSecretId, + methodName: this.methodName, + } + + for (const [key, value] of Object.entries(this.credential.credentialSubject)) { + tags[`attr::${key}::value`] = value + tags[`attr::${key}::marker`] = true + } + + return tags + } +} diff --git a/packages/anoncreds/src/repository/AnonCredsW3CCredentialRepository.ts b/packages/anoncreds/src/repository/AnonCredsW3CCredentialRepository.ts new file mode 100644 index 0000000000..d48c3f51a8 --- /dev/null +++ b/packages/anoncreds/src/repository/AnonCredsW3CCredentialRepository.ts @@ -0,0 +1,31 @@ +import type { AgentContext } from '@aries-framework/core' + +import { Repository, InjectionSymbols, StorageService, EventEmitter, injectable, inject } from '@aries-framework/core' + +import { AnonCredsW3CCredentialRecord } from './AnonCredsW3CCredentialRecord' + +@injectable() +export class AnonCredsW3CCredentialRepository extends Repository { + public constructor( + @inject(InjectionSymbols.StorageService) storageService: StorageService, + eventEmitter: EventEmitter + ) { + super(AnonCredsW3CCredentialRecord, 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 }) + } + + public async getByCredentialId(agentContext: AgentContext, credentialId: string) { + return this.getSingleByQuery(agentContext, { credentialId }) + } + + public async findByCredentialId(agentContext: AgentContext, credentialId: string) { + return this.findSingleByQuery(agentContext, { credentialId }) + } +} diff --git a/packages/anoncreds/src/repository/index.ts b/packages/anoncreds/src/repository/index.ts index c4fb3bbe80..a33b4ce9a2 100644 --- a/packages/anoncreds/src/repository/index.ts +++ b/packages/anoncreds/src/repository/index.ts @@ -1,5 +1,5 @@ -export * from './AnonCredsCredentialRecord' -export * from './AnonCredsCredentialRepository' +export * from './AnonCredsW3CCredentialRecord' +export * from './AnonCredsW3CCredentialRepository' export * from './AnonCredsCredentialDefinitionRecord' export * from './AnonCredsCredentialDefinitionRepository' export * from './AnonCredsCredentialDefinitionPrivateRecord' diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index 568d17c0c0..bae0bd2e93 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -60,6 +60,7 @@ export interface StoreCredentialOptions { export interface GetCredentialOptions { credentialId: string + isLegacy?: boolean } export interface GetCredentialsOptions { @@ -84,6 +85,7 @@ export interface GetCredentialsForProofRequestOptions { start?: number limit?: number extraQuery?: ReferentWalletQuery + isLegacy?: boolean } export type GetCredentialsForProofRequestReturn = Array<{ diff --git a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts index 936badad1b..adea238c5a 100644 --- a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts @@ -1,4 +1,4 @@ -import type { AnonCredsProofRequest } from '../models/exchange' +import type { AnonCredsProof, AnonCredsProofRequest } from '../models/exchange' import type { AnonCredsCredentialDefinition, AnonCredsRevocationStatusList, @@ -10,7 +10,7 @@ import { AnonCredsW3CPresentation } from '../models/exchange' export interface VerifyProofOptions { proofRequest: AnonCredsProofRequest - proof: AnonCredsW3CPresentation + proof: AnonCredsW3CPresentation | AnonCredsProof schemas: { [schemaId: string]: AnonCredsSchema } diff --git a/packages/anoncreds/src/utils/getRevocationRegistries.ts b/packages/anoncreds/src/utils/getRevocationRegistries.ts index b16556694c..dd9db2d8e5 100644 --- a/packages/anoncreds/src/utils/getRevocationRegistries.ts +++ b/packages/anoncreds/src/utils/getRevocationRegistries.ts @@ -8,7 +8,7 @@ import { AnonCredsRegistryService } from '../services' import { assertBestPracticeRevocationInterval } from './revocationInterval' import { downloadTailsFile } from './tails' -import { AnonCredsW3CPresentation } from "../models"; +import { AnonCredsW3CPresentation } from '../models' export async function getRevocationRegistriesForRequest( agentContext: AgentContext, @@ -157,7 +157,7 @@ export async function getRevocationRegistriesForRequest( } } -export async function getRevocationRegistriesForProof(agentContext: AgentContext, proof: AnonCredsW3CPresentation) { +export async function getRevocationRegistriesForProofW3C(agentContext: AgentContext, proof: AnonCredsW3CPresentation) { const revocationRegistries: VerifyProofOptions['revocationRegistries'] = {} const revocationRegistryPromises = [] @@ -210,3 +210,57 @@ export async function getRevocationRegistriesForProof(agentContext: AgentContext await Promise.all(revocationRegistryPromises) return revocationRegistries } + +export async function getRevocationRegistriesForProof(agentContext: AgentContext, proof: AnonCredsProof) { + const revocationRegistries: VerifyProofOptions['revocationRegistries'] = {} + + const revocationRegistryPromises = [] + for (const identifier of proof.identifiers) { + const revocationRegistryId = identifier.rev_reg_id + const timestamp = identifier.timestamp + + // Skip if no revocation registry id is present + if (!revocationRegistryId || !timestamp) continue + + const registry = agentContext.dependencyManager + .resolve(AnonCredsRegistryService) + .getRegistryForIdentifier(agentContext, revocationRegistryId) + + const getRevocationRegistry = async () => { + // Fetch revocation registry definition if not already fetched + if (!revocationRegistries[revocationRegistryId]) { + const { revocationRegistryDefinition, resolutionMetadata } = await registry.getRevocationRegistryDefinition( + agentContext, + revocationRegistryId + ) + if (!revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Could not retrieve revocation registry definition for revocation registry ${revocationRegistryId}: ${resolutionMetadata.message}` + ) + } + + revocationRegistries[revocationRegistryId] = { + definition: revocationRegistryDefinition, + revocationStatusLists: {}, + } + } + + // Fetch revocation status list by timestamp if not already fetched + if (!revocationRegistries[revocationRegistryId].revocationStatusLists[timestamp]) { + const { revocationStatusList, resolutionMetadata: statusListResolutionMetadata } = + await registry.getRevocationStatusList(agentContext, revocationRegistryId, timestamp) + + if (!revocationStatusList) { + throw new AriesFrameworkError( + `Could not retrieve revocation status list for revocation registry ${revocationRegistryId}: ${statusListResolutionMetadata.message}` + ) + } + + revocationRegistries[revocationRegistryId].revocationStatusLists[timestamp] = revocationStatusList + } + } + revocationRegistryPromises.push(getRevocationRegistry()) + } + await Promise.all(revocationRegistryPromises) + return revocationRegistries +} diff --git a/packages/core/src/modules/proofs/ProofsApi.ts b/packages/core/src/modules/proofs/ProofsApi.ts index 483ad5e2c1..fe5f12deb1 100644 --- a/packages/core/src/modules/proofs/ProofsApi.ts +++ b/packages/core/src/modules/proofs/ProofsApi.ts @@ -262,6 +262,7 @@ export class ProofsApi implements ProofsApi { comment: options.comment, goalCode: options.goalCode, willConfirm: options.willConfirm, + isW3C: options.isW3C, }) const outboundMessageContext = await getOutboundMessageContext(this.agentContext, { diff --git a/packages/core/src/modules/proofs/ProofsApiOptions.ts b/packages/core/src/modules/proofs/ProofsApiOptions.ts index 0e9911febc..21f84846cd 100644 --- a/packages/core/src/modules/proofs/ProofsApiOptions.ts +++ b/packages/core/src/modules/proofs/ProofsApiOptions.ts @@ -78,6 +78,7 @@ export interface CreateProofRequestOptions { proofRecord: ProofExchangeRecord proofFormats: ProofFormatPayload<[PF], 'createRequest'> attachmentId?: string + isW3C?: boolean } export interface ProofFormatAcceptRequestOptions { diff --git a/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts b/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts index ff752beb29..e7befda52b 100644 --- a/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts +++ b/packages/core/src/modules/proofs/protocol/ProofProtocolOptions.ts @@ -108,6 +108,7 @@ export interface CreateProofRequestOptions ext /** @default true */ willConfirm?: boolean + isW3C?: boolean } export interface AcceptProofRequestOptions extends BaseOptions { diff --git a/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts b/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts index d83b621e02..8bb17b204d 100644 --- a/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts +++ b/packages/core/src/modules/proofs/protocol/v2/ProofFormatCoordinator.ts @@ -188,6 +188,7 @@ export class ProofFormatCoordinator { goalCode, presentMultiple, willConfirm, + isW3C, }: { formatServices: ProofFormatService[] proofFormats: ProofFormatPayload, 'createRequest'> @@ -196,6 +197,7 @@ export class ProofFormatCoordinator { goalCode?: string presentMultiple?: boolean willConfirm?: boolean + isW3C?: boolean } ): Promise { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -208,6 +210,7 @@ export class ProofFormatCoordinator { const { format, attachment } = await formatService.createRequest(agentContext, { proofFormats, proofRecord, + isW3C, }) requestAttachments.push(attachment) diff --git a/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts b/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts index ffec69b8a3..c697c638a2 100644 --- a/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts +++ b/packages/core/src/modules/proofs/protocol/v2/V2ProofProtocol.ts @@ -334,6 +334,7 @@ export class V2ProofProtocol ): Promise> { const proofRepository = agentContext.dependencyManager.resolve(ProofRepository) @@ -359,6 +360,7 @@ export class V2ProofProtocol Date: Thu, 23 Nov 2023 20:35:20 +0500 Subject: [PATCH 5/8] refactoring: split into separate methods, add logs --- demo/src/BaseAgent.ts | 3 - demo/src/Faber.ts | 4 +- demo/src/FaberInquirer.ts | 7 +- .../src/services/AnonCredsRsHolderService.ts | 225 ++++++++++++++---- .../services/AnonCredsRsVerifierService.ts | 69 +++++- .../formats/AnonCredsProofFormatService.ts | 42 ++-- 6 files changed, 265 insertions(+), 85 deletions(-) diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index af11c3b742..c2e787e32a 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -31,8 +31,6 @@ import { CredentialsModule, Agent, HttpOutboundTransport, - ConsoleLogger, - LogLevel, } from '@aries-framework/core' import { IndySdkAnonCredsRegistry, IndySdkModule, IndySdkSovDidResolver } from '@aries-framework/indy-sdk' import { IndyVdrIndyDidResolver, IndyVdrAnonCredsRegistry, IndyVdrModule } from '@aries-framework/indy-vdr' @@ -86,7 +84,6 @@ export class BaseAgent { id: name, key: name, }, - logger: new ConsoleLogger(LogLevel.trace), endpoints: [`http://localhost:${this.port}`], } satisfies InitConfig diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index c66ff61189..b48f5b4d11 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -251,7 +251,7 @@ export class Faber extends BaseAgent { return proofAttribute } - public async sendProofRequest() { + public async sendProofRequest(isW3C?: boolean) { const connectionRecord = await this.getConnectionRecord() const proofAttribute = await this.newProofAttribute() await this.printProofFlow(greenText('\nRequesting proof...\n', false)) @@ -266,7 +266,7 @@ export class Faber extends BaseAgent { requested_attributes: proofAttribute, }, }, - isW3C: true, + isW3C: isW3C, }) this.ui.updateBottomBar( `\nProof request sent!\n\nGo to the Alice agent to accept the proof request\n\n${Color.Reset}` diff --git a/demo/src/FaberInquirer.ts b/demo/src/FaberInquirer.ts index 7eb4ac7785..87dd55b96c 100644 --- a/demo/src/FaberInquirer.ts +++ b/demo/src/FaberInquirer.ts @@ -97,7 +97,12 @@ export class FaberInquirer extends BaseInquirer { } public async proof() { - await this.faber.sendProofRequest() + let isW3C = true + const confirm = await prompt([this.inquireConfirmation('Should be the proof in W3C format?')]) + if (confirm.options === ConfirmOptions.No) { + isW3C = false + } + await this.faber.sendProofRequest(isW3C) const title = 'Is the proof request accepted?' await this.listener.newAcceptedPrompt(title, this) } diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index 29cf412d57..9639691b05 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -20,6 +20,16 @@ import type { GetCredentialsOptions, StoreCredentialOptions, } from '@aries-framework/anoncreds' +import type { AgentContext, Query, SimpleQuery } from '@aries-framework/core' +import type { CredentialEntry } from '@hyperledger/anoncreds-nodejs' +import type { + CredentialProve, + CredentialRequestMetadata, + JsonObject, + W3CCredentialEntry, + Credential, +} from '@hyperledger/anoncreds-shared' + import { AnonCredsLinkSecretRepository, AnonCredsRegistryService, @@ -29,17 +39,11 @@ import { storeLinkSecret, unqualifiedCredentialDefinitionIdRegex, } from '@aries-framework/anoncreds' -import type { AgentContext, Query, SimpleQuery } from '@aries-framework/core' -import { AriesFrameworkError, injectable, JsonTransformer, TypedArrayEncoder, utils} from '@aries-framework/core' -import type { - CredentialProve, - CredentialRequestMetadata, - JsonObject, - W3CCredentialEntry, -} from '@hyperledger/anoncreds-shared' +import { AnonCredsCredentialRecord } from '@aries-framework/anoncreds/src/repository/AnonCredsCredentialRecord' +import { AnonCredsCredentialRepository } from '@aries-framework/anoncreds/src/repository/AnonCredsCredentialRepository' +import { AriesFrameworkError, injectable, JsonTransformer, TypedArrayEncoder, utils } from '@aries-framework/core' import { anoncreds, - Credential, CredentialRevocationState, LinkSecret, Presentation, @@ -49,12 +53,10 @@ import { W3CCredentialRequest, W3CPresentation, } from '@hyperledger/anoncreds-shared' -import { AnonCredsCredentialRecord } from '@aries-framework/anoncreds/src/repository/AnonCredsCredentialRecord' -import { AnonCredsCredentialRepository } from '@aries-framework/anoncreds/src/repository/AnonCredsCredentialRepository' +import * as console from 'console' import { AnonCredsRsModuleConfig } from '../AnonCredsRsModuleConfig' import { AnonCredsRsError } from '../errors/AnonCredsRsError' -import { CredentialEntry } from "@hyperledger/anoncreds-nodejs"; @injectable() export class AnonCredsRsHolderService implements AnonCredsHolderService { @@ -69,9 +71,17 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { + const { proofRequest } = options + if (proofRequest.isW3C) { + return this.createProofW3C(agentContext, options) + } else { + return this.createProofLegacy(agentContext, options) + } + } + public async createProofW3C(agentContext: AgentContext, options: CreateProofOptions): Promise { const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options - let presentation: W3CPresentation | Presentation | undefined + let presentation: W3CPresentation | undefined try { const rsCredentialDefinitions: Record = {} for (const credDefId in credentialDefinitions) { @@ -82,20 +92,14 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { for (const schemaId in schemas) { rsSchemas[schemaId] = schemas[schemaId] as unknown as JsonObject } - let credentialRepository: AnonCredsW3CCredentialRepository | AnonCredsCredentialRepository - - if (proofRequest.isW3C) { - credentialRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) - } else { - credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) - } + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) // Cache retrieved credentials in order to minimize storage calls - const retrievedCredentials = new Map() + const retrievedCredentials = new Map() const credentialEntryFromAttribute = async ( attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch - ): Promise<{ linkSecretId: string; credentialEntry: W3CCredentialEntry | CredentialEntry }> => { + ): Promise<{ linkSecretId: string; credentialEntry: W3CCredentialEntry }> => { let credentialRecord = retrievedCredentials.get(attribute.credentialId) if (!credentialRecord) { credentialRecord = await credentialRepository.getByCredentialId(agentContext, attribute.credentialId) @@ -103,7 +107,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } // @ts-ignore - const revocationRegistryDefinitionId = credentialRecord.credential.credentialStatus?.id || credentialRecord?.credential?.rev_reg_id + const revocationRegistryDefinitionId = credentialRecord.credential.credentialStatus?.id const revocationRegistryIndex = credentialRecord.credentialRevocationId // TODO: Check if credential has a revocation registry id (check response from anoncreds-rs API, as it is @@ -152,7 +156,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } const credentialsProve: CredentialProve[] = [] - const credentials: { linkSecretId: string; credentialEntry: W3CCredentialEntry | CredentialEntry }[] = [] + const credentials: { linkSecretId: string; credentialEntry: W3CCredentialEntry }[] = [] let entryIndex = 0 for (const referent in selectedCredentials.attributes) { @@ -183,27 +187,144 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { throw new AnonCredsRsError('Link Secret value not stored') } - if (proofRequest.isW3C) { - presentation = W3CPresentation.create({ - credentialDefinitions: rsCredentialDefinitions, - schemas: rsSchemas, - presentationRequest: proofRequest as unknown as JsonObject, - credentials: credentials.map((entry) => entry.credentialEntry as W3CCredentialEntry), - credentialsProve, - // selfAttest: selectedCredentials.selfAttestedAttributes, - linkSecret: linkSecretRecord.value, - }) - } else { - presentation = Presentation.create({ - credentialDefinitions: rsCredentialDefinitions, - schemas: rsSchemas, - presentationRequest: proofRequest as unknown as JsonObject, - credentials: credentials.map((entry) => entry.credentialEntry as CredentialEntry), - credentialsProve, - selfAttest: selectedCredentials.selfAttestedAttributes, - linkSecret: linkSecretRecord.value, - }) + presentation = W3CPresentation.create({ + credentialDefinitions: rsCredentialDefinitions, + schemas: rsSchemas, + presentationRequest: proofRequest as unknown as JsonObject, + credentials: credentials.map((entry) => entry.credentialEntry), + credentialsProve, + // selfAttest: selectedCredentials.selfAttestedAttributes, + linkSecret: linkSecretRecord.value, + }) + console.log('\n------------Created presentation in W3C format------------') + console.dir(presentation.toJson(), { depth: null }) + console.log('------------------------------------------------------\n') + + return presentation.toJson() as unknown as AnonCredsProof + } finally { + presentation?.handle.clear() + } + } + public async createProofLegacy(agentContext: AgentContext, options: CreateProofOptions): Promise { + const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options + + let presentation: Presentation | undefined + try { + const rsCredentialDefinitions: Record = {} + for (const credDefId in credentialDefinitions) { + rsCredentialDefinitions[credDefId] = credentialDefinitions[credDefId] as unknown as JsonObject + } + + const rsSchemas: Record = {} + for (const schemaId in schemas) { + rsSchemas[schemaId] = schemas[schemaId] as unknown as JsonObject } + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + + // Cache retrieved credentials in order to minimize storage calls + const retrievedCredentials = new Map() + + const credentialEntryFromAttribute = async ( + attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch + ): Promise<{ linkSecretId: string; credentialEntry: CredentialEntry }> => { + let credentialRecord = retrievedCredentials.get(attribute.credentialId) + if (!credentialRecord) { + credentialRecord = await credentialRepository.getByCredentialId(agentContext, attribute.credentialId) + retrievedCredentials.set(attribute.credentialId, credentialRecord) + } + + const revocationRegistryDefinitionId = credentialRecord.credential.rev_reg_id + const revocationRegistryIndex = credentialRecord.credentialRevocationId + + // TODO: Check if credential has a revocation registry id (check response from anoncreds-rs API, as it is + // sending back a mandatory string in Credential.revocationRegistryId) + const timestamp = attribute.timestamp + + let revocationState: CredentialRevocationState | undefined + let revocationRegistryDefinition: RevocationRegistryDefinition | undefined + try { + if (timestamp && revocationRegistryIndex && revocationRegistryDefinitionId) { + if (!options.revocationRegistries[revocationRegistryDefinitionId]) { + throw new AnonCredsRsError(`Revocation Registry ${revocationRegistryDefinitionId} not found`) + } + + const { definition, revocationStatusLists, tailsFilePath } = + options.revocationRegistries[revocationRegistryDefinitionId] + + // Extract revocation status list for the given timestamp + const revocationStatusList = revocationStatusLists[timestamp] + if (!revocationStatusList) { + throw new AriesFrameworkError( + `Revocation status list for revocation registry ${revocationRegistryDefinitionId} and timestamp ${timestamp} not found in revocation status lists. All revocation status lists must be present.` + ) + } + + revocationRegistryDefinition = RevocationRegistryDefinition.fromJson(definition as unknown as JsonObject) + revocationState = CredentialRevocationState.create({ + revocationRegistryIndex: Number(revocationRegistryIndex), + revocationRegistryDefinition, + tailsPath: tailsFilePath, + revocationStatusList: RevocationStatusList.fromJson(revocationStatusList as unknown as JsonObject), + }) + } + return { + linkSecretId: credentialRecord.linkSecretId, + credentialEntry: { + credential: credentialRecord.credential as unknown as JsonObject, + revocationState: revocationState?.toJson(), + timestamp, + }, + } + } finally { + revocationState?.handle.clear() + revocationRegistryDefinition?.handle.clear() + } + } + + const credentialsProve: CredentialProve[] = [] + const credentials: { linkSecretId: string; credentialEntry: CredentialEntry }[] = [] + + let entryIndex = 0 + 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 selectedCredentials.predicates) { + const predicate = selectedCredentials.predicates[referent] + credentials.push(await credentialEntryFromAttribute(predicate)) + credentialsProve.push({ entryIndex, isPredicate: true, referent, reveal: true }) + entryIndex = entryIndex + 1 + } + + // Get all requested credentials and take linkSecret. If it's not the same for every credential, throw error + const linkSecretsMatch = credentials.every((item) => item.linkSecretId === credentials[0].linkSecretId) + if (!linkSecretsMatch) { + throw new AnonCredsRsError('All credentials in a Proof should have been issued using the same Link Secret') + } + + const linkSecretRecord = await agentContext.dependencyManager + .resolve(AnonCredsLinkSecretRepository) + .getByLinkSecretId(agentContext, credentials[0].linkSecretId) + + if (!linkSecretRecord.value) { + throw new AnonCredsRsError('Link Secret value not stored') + } + + presentation = Presentation.create({ + credentialDefinitions: rsCredentialDefinitions, + schemas: rsSchemas, + presentationRequest: proofRequest as unknown as JsonObject, + credentials: credentials.map((entry) => entry.credentialEntry as CredentialEntry), + credentialsProve, + selfAttest: selectedCredentials.selfAttestedAttributes, + linkSecret: linkSecretRecord.value, + }) + console.log('\n------------Created presentation in Old format------------') + console.dir(presentation.toJson(), { depth: null }) + console.log('------------------------------------------------------\n') return presentation.toJson() as unknown as AnonCredsProof } finally { @@ -316,16 +437,12 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { }) ) + console.log('\n------------Saved credential in W3C format------------') + console.log(processedCredential.toJson()) + console.log('------------------------------------------------------\n') + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) - // credentialObj = Credential.fromW3C({ credential: credentialObj }) - // credentialObj = Credential.fromJson(credentialObj.toLegacy().toJson()) - // processedCredential = credentialObj.process({ - // credentialDefinition: credentialDefinition as unknown as JsonObject, - // credentialRequestMetadata: credentialRequestMetadata as unknown as JsonObject, - // linkSecret: linkSecretRecord.value, - // revocationRegistryDefinition, - // }) await credentialRepository.save( agentContext, new AnonCredsCredentialRecord({ @@ -341,6 +458,10 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { }) ) + console.log('\n------------Saved credential in Old format------------') + console.log(processedCredential.toLegacy().toJson()) + console.log('------------------------------------------------------\n') + return credentialId } finally { credentialObj?.handle.clear() diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts index 0c88b51c2a..497f541a48 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts @@ -4,19 +4,71 @@ import type { JsonObject } from '@hyperledger/anoncreds-shared' import { injectable } from '@aries-framework/core' import { Presentation, W3CPresentation } from '@hyperledger/anoncreds-shared' +import console from 'console' @injectable() export class AnonCredsRsVerifierService implements AnonCredsVerifierService { public async verifyProof(agentContext: AgentContext, options: VerifyProofOptions): Promise { + const { proofRequest } = options + + if (proofRequest.isW3C) { + return this.verifyProofW3C(agentContext, options) + } else { + return this.verifyProofLegacy(agentContext, options) + } + } + public async verifyProofW3C(agentContext: AgentContext, options: VerifyProofOptions): Promise { const { credentialDefinitions, proof, proofRequest, revocationRegistries, schemas } = options - let presentation: W3CPresentation | Presentation | undefined + let presentation: W3CPresentation | undefined try { - if (proofRequest.isW3C) { - presentation = W3CPresentation.fromJson(proof as unknown as JsonObject) - } else { - presentation = Presentation.fromJson(proof as unknown as JsonObject) + presentation = W3CPresentation.fromJson(proof as unknown as JsonObject) + + const rsCredentialDefinitions: Record = {} + for (const credDefId in credentialDefinitions) { + rsCredentialDefinitions[credDefId] = credentialDefinitions[credDefId] as unknown as JsonObject } + + const rsSchemas: Record = {} + for (const schemaId in schemas) { + rsSchemas[schemaId] = schemas[schemaId] as unknown as JsonObject + } + + const revocationRegistryDefinitions: Record = {} + const lists: JsonObject[] = [] + + for (const revocationRegistryDefinitionId in revocationRegistries) { + const { definition, revocationStatusLists } = options.revocationRegistries[revocationRegistryDefinitionId] + + revocationRegistryDefinitions[revocationRegistryDefinitionId] = definition as unknown as JsonObject + + lists.push(...(Object.values(revocationStatusLists) as unknown as Array)) + } + + const result = presentation.verify({ + presentationRequest: proofRequest as unknown as JsonObject, + credentialDefinitions: rsCredentialDefinitions, + schemas: rsSchemas, + revocationRegistryDefinitions, + revocationStatusLists: lists, + }) + console.log('\n------------Verified presentation in W3C format------------') + console.dir(presentation.toJson(), { depth: null }) + console.log('------------------------------------------------------\n') + + return result + } finally { + presentation?.handle.clear() + } + } + + public async verifyProofLegacy(agentContext: AgentContext, options: VerifyProofOptions): Promise { + const { credentialDefinitions, proof, proofRequest, revocationRegistries, schemas } = options + + let presentation: Presentation | undefined + try { + presentation = Presentation.fromJson(proof as unknown as JsonObject) + const rsCredentialDefinitions: Record = {} for (const credDefId in credentialDefinitions) { rsCredentialDefinitions[credDefId] = credentialDefinitions[credDefId] as unknown as JsonObject @@ -38,13 +90,18 @@ export class AnonCredsRsVerifierService implements AnonCredsVerifierService { lists.push(...(Object.values(revocationStatusLists) as unknown as Array)) } - return presentation.verify({ + const result = presentation.verify({ presentationRequest: proofRequest as unknown as JsonObject, credentialDefinitions: rsCredentialDefinitions, schemas: rsSchemas, revocationRegistryDefinitions, revocationStatusLists: lists, }) + console.log('\n------------Verified presentation in Old format------------') + console.dir(presentation.toJson(), { depth: null }) + console.log('------------------------------------------------------\n') + + return result } finally { presentation?.handle.clear() } diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts index 0622e173de..af31b54474 100644 --- a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts @@ -226,27 +226,27 @@ export class AnonCredsProofFormatService implements ProofFormatService() - // for (const [referent, attribute] of Object.entries(proofJson.requested_proof.revealed_attrs)) { - // if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { - // throw new AriesFrameworkError( - // `The encoded value for '${referent}' is invalid. ` + - // `Expected '${encodeCredentialValue(attribute.raw)}'. ` + - // `Actual '${attribute.encoded}'` - // ) - // } - // } - // - // for (const [, attributeGroup] of Object.entries(proofJson.requested_proof.revealed_attr_groups ?? {})) { - // for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { - // if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { - // throw new AriesFrameworkError( - // `The encoded value for '${attributeName}' is invalid. ` + - // `Expected '${encodeCredentialValue(attribute.raw)}'. ` + - // `Actual '${attribute.encoded}'` - // ) - // } - // } - // } + for (const [referent, attribute] of Object.entries(proofJson.requested_proof.revealed_attrs)) { + if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + throw new AriesFrameworkError( + `The encoded value for '${referent}' is invalid. ` + + `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + + for (const [, attributeGroup] of Object.entries(proofJson.requested_proof.revealed_attr_groups ?? {})) { + for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { + if (!checkValidCredentialValueEncoding(attribute.raw, attribute.encoded)) { + throw new AriesFrameworkError( + `The encoded value for '${attributeName}' is invalid. ` + + `Expected '${encodeCredentialValue(attribute.raw)}'. ` + + `Actual '${attribute.encoded}'` + ) + } + } + } const schemas = await this.getSchemas(agentContext, new Set(proofJson.identifiers.map((i) => i.schema_id))) const credentialDefinitions = await this.getCredentialDefinitions( From 53d37d44189c3dedb61e2d07ccf207fa9b12f7e7 Mon Sep 17 00:00:00 2001 From: Abdulbois Date: Fri, 24 Nov 2023 18:10:17 +0500 Subject: [PATCH 6/8] refactoring: enable to send and accept legacy and W3C credential --- demo/src/AliceInquirer.ts | 4 +- .../src/services/AnonCredsRsHolderService.ts | 48 ++++-- .../src/services/AnonCredsRsIssuerService.ts | 82 ++++++++- .../AnonCredsCredentialFormatService.ts | 155 ++++++++++++------ packages/anoncreds/src/models/exchange.ts | 1 + packages/anoncreds/src/models/internal.ts | 1 + .../services/AnonCredsHolderServiceOptions.ts | 1 + .../src/modules/credentials/CredentialsApi.ts | 1 + .../credentials/CredentialsApiOptions.ts | 1 + .../formats/CredentialFormatServiceOptions.ts | 1 + .../protocol/CredentialProtocolOptions.ts | 1 + .../v2/CredentialFormatCoordinator.ts | 3 + .../protocol/v2/V2CredentialProtocol.ts | 3 +- 13 files changed, 237 insertions(+), 65 deletions(-) diff --git a/demo/src/AliceInquirer.ts b/demo/src/AliceInquirer.ts index a665d5e80b..7400a1c6bd 100644 --- a/demo/src/AliceInquirer.ts +++ b/demo/src/AliceInquirer.ts @@ -74,7 +74,9 @@ export class AliceInquirer extends BaseInquirer { if (confirm.options === ConfirmOptions.No) { await this.alice.agent.credentials.declineOffer(credentialRecord.id) } else if (confirm.options === ConfirmOptions.Yes) { - await this.alice.acceptCredentialOffer(credentialRecord) + const confirm = await prompt([this.inquireConfirmation('Do you want to get credential in W3C format')]) + + await this.alice.acceptCredentialOffer(credentialRecord, confirm.options == ConfirmOptions.Yes) } } diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index 9639691b05..53314369c2 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -42,6 +42,7 @@ import { import { AnonCredsCredentialRecord } from '@aries-framework/anoncreds/src/repository/AnonCredsCredentialRecord' import { AnonCredsCredentialRepository } from '@aries-framework/anoncreds/src/repository/AnonCredsCredentialRepository' import { AriesFrameworkError, injectable, JsonTransformer, TypedArrayEncoder, utils } from '@aries-framework/core' +import { CredentialRequest } from '@hyperledger/anoncreds-nodejs' import { anoncreds, CredentialRevocationState, @@ -338,7 +339,10 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { ): Promise { const { useLegacyProverDid, credentialDefinition, credentialOffer } = options let createReturnObj: - | { credentialRequest: W3CCredentialRequest; credentialRequestMetadata: CredentialRequestMetadata } + | { + credentialRequest: W3CCredentialRequest | CredentialRequest + credentialRequestMetadata: CredentialRequestMetadata + } | undefined try { const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) @@ -368,19 +372,35 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { if (!isLegacyIdentifier && useLegacyProverDid) { throw new AriesFrameworkError('Cannot use legacy prover_did with non-legacy identifiers') } - createReturnObj = W3CCredentialRequest.create({ - 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, - linkSecret: linkSecretRecord.value, - linkSecretId: linkSecretRecord.linkSecretId, - }) + + if (options.isW3C) { + createReturnObj = W3CCredentialRequest.create({ + 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, + linkSecret: linkSecretRecord.value, + linkSecretId: linkSecretRecord.linkSecretId, + }) + } else { + createReturnObj = CredentialRequest.create({ + 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, + linkSecret: linkSecretRecord.value, + linkSecretId: linkSecretRecord.linkSecretId, + }) + } + const credRequest = createReturnObj.credentialRequest.toJson() as unknown as AnonCredsCredentialRequest + credRequest.isW3C = options.isW3C return { - credentialRequest: createReturnObj.credentialRequest.toJson() as unknown as AnonCredsCredentialRequest, + credentialRequest: credRequest, credentialRequestMetadata: createReturnObj.credentialRequestMetadata.toJson() as unknown as AnonCredsCredentialRequestMetadata, } @@ -405,8 +425,8 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { const credentialId = options.credentialId ?? utils.uuid() - let credentialObj: W3CCredential | Credential | undefined - let processedCredential: W3CCredential | Credential | undefined + let credentialObj: W3CCredential | undefined + let processedCredential: W3CCredential | undefined try { credentialObj = W3CCredential.fromJson(credential as unknown as JsonObject) processedCredential = credentialObj.process({ diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts index ab98a23aa6..35f54fed27 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts @@ -24,7 +24,13 @@ import { AnonCredsCredentialDefinitionRepository, } from '@aries-framework/anoncreds' import { injectable, AriesFrameworkError } from '@aries-framework/core' -import { W3CCredential, CredentialDefinition, W3CCredentialOffer, Schema } from '@hyperledger/anoncreds-shared' +import { + Credential, + W3CCredential, + CredentialDefinition, + W3CCredentialOffer, + Schema, +} from '@hyperledger/anoncreds-shared' import { AnonCredsRsError } from '../errors/AnonCredsRsError' @@ -131,6 +137,80 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { public async createCredential( agentContext: AgentContext, options: CreateCredentialOptions + ): Promise { + if (options.credentialRequest.isW3C) { + return this.createCredentialW3c(agentContext, options) + } else { + return this.createCredentialLegacy(agentContext, options) + } + } + + public async createCredentialLegacy( + agentContext: AgentContext, + options: CreateCredentialOptions + ): Promise { + const { tailsFilePath, credentialOffer, credentialRequest, credentialValues, revocationRegistryId } = options + + let credential: Credential | undefined + try { + if (revocationRegistryId || tailsFilePath) { + throw new AriesFrameworkError('Revocation not supported yet') + } + + const attributeRawValues: Record = {} + const attributeEncodedValues: Record = {} + + Object.keys(credentialValues).forEach((key) => { + attributeRawValues[key] = credentialValues[key].raw + attributeEncodedValues[key] = credentialValues[key].encoded + }) + + const credentialDefinitionRecord = await agentContext.dependencyManager + .resolve(AnonCredsCredentialDefinitionRepository) + .getByCredentialDefinitionId(agentContext, options.credentialRequest.cred_def_id) + + // We fetch the private record based on the cred def id from the cred def record, as the + // credential definition id passed to this module could be unqualified, and the private record + // is only stored using the qualified identifier. + const credentialDefinitionPrivateRecord = await agentContext.dependencyManager + .resolve(AnonCredsCredentialDefinitionPrivateRepository) + .getByCredentialDefinitionId(agentContext, credentialDefinitionRecord.credentialDefinitionId) + + let credentialDefinition = credentialDefinitionRecord.credentialDefinition + + if (isUnqualifiedCredentialDefinitionId(options.credentialRequest.cred_def_id)) { + const { namespaceIdentifier, schemaName, schemaVersion } = parseIndySchemaId(credentialDefinition.schemaId) + const { namespaceIdentifier: unqualifiedDid } = parseIndyDid(credentialDefinition.issuerId) + parseIndyDid + credentialDefinition = { + ...credentialDefinition, + schemaId: getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion), + issuerId: unqualifiedDid, + } + } + + credential = Credential.create({ + credentialDefinition: credentialDefinitionRecord.credentialDefinition as unknown as JsonObject, + credentialOffer: credentialOffer as unknown as JsonObject, + credentialRequest: credentialRequest as unknown as JsonObject, + revocationRegistryId, + // attributeEncodedValues, + attributeRawValues, + credentialDefinitionPrivate: credentialDefinitionPrivateRecord.value, + }) + + return { + credential: credential.toJson() as unknown as AnonCredsCredential, + credentialRevocationId: credential.revocationRegistryIndex?.toString(), + } + } finally { + credential?.handle.clear() + } + } + + public async createCredentialW3c( + agentContext: AgentContext, + options: CreateCredentialOptions ): Promise { const { tailsFilePath, credentialOffer, credentialRequest, credentialValues, revocationRegistryId } = options diff --git a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts index fc5c80c5b7..35d4ef2bd1 100644 --- a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts @@ -6,7 +6,13 @@ import type { AnonCredsCredentialRequestMetadata, AnonCredsW3CCredential, } from '../models' -import type { AnonCredsIssuerService, AnonCredsHolderService, GetRevocationRegistryDefinitionReturn } from '../services' +import type { + AnonCredsIssuerService, + AnonCredsHolderService, + GetRevocationRegistryDefinitionReturn, + GetCredentialDefinitionReturn, + GetSchemaReturn, +} from '../services' import type { AnonCredsCredentialMetadata } from '../utils/metadata' import type { CredentialFormatService, @@ -29,6 +35,7 @@ import type { CredentialPreviewAttributeOptions, LinkedAttachment, } from '@aries-framework/core' +import type { JsonObject } from '@hyperledger/anoncreds-shared' import { ProblemReportError, @@ -41,6 +48,8 @@ import { CredentialProblemReportReason, JsonTransformer, } from '@aries-framework/core' +import { Credential, W3CCredential } from '@hyperledger/anoncreds-shared' +import { Schema } from '@hyperledger/anoncreds-shared/api/Schema' import { AnonCredsError } from '../error' import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' @@ -54,6 +63,7 @@ import { createAndLinkAttachmentsToPreview, } from '../utils/credential' import { AnonCredsCredentialMetadataKey, AnonCredsCredentialRequestMetadataKey } from '../utils/metadata' +import console from 'console' const ANONCREDS_CREDENTIAL_OFFER = 'anoncreds/credential-offer@v1.0' const ANONCREDS_CREDENTIAL_REQUEST = 'anoncreds/credential-request@v1.0' @@ -219,6 +229,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService attachmentId, offerAttachment, credentialFormats, + isW3C, }: CredentialFormatAcceptOfferOptions ): Promise { const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) @@ -243,8 +254,11 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService credentialOffer, credentialDefinition, linkSecretId: credentialFormats?.anoncreds?.linkSecretId, + isW3C: isW3C, }) + credentialRequestMetadata.isW3C = isW3C + credentialRecord.metadata.set( AnonCredsCredentialRequestMetadataKey, credentialRequestMetadata @@ -358,44 +372,93 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService 'Missing credential attributes on credential record. Unable to check credential attributes' ) } + let credentialDefinitionResult: GetCredentialDefinitionReturn + let schemaResult: GetSchemaReturn + let revocationRegistryResult: null | GetRevocationRegistryDefinitionReturn = null + let anonCredsCredential: AnonCredsW3CCredential - const anonCredsCredential = attachment.getDataAsJson() + console.log('\n------------Processing an incoming credential------------') + console.log(attachment.getDataAsJson()) + console.log('------------------------------------------------------\n') - const credentialDefinitionResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialSchema.definition) - .getCredentialDefinition(agentContext, anonCredsCredential.credentialSchema.definition) - if (!credentialDefinitionResult.credentialDefinition) { - throw new AriesFrameworkError( - `Unable to resolve credential definition ${anonCredsCredential.credentialSchema.definition}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` - ) - } + if (credentialRequestMetadata.isW3C) { + anonCredsCredential = attachment.getDataAsJson() - const schemaResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialSchema.definition) - .getSchema(agentContext, anonCredsCredential.credentialSchema.schema) - if (!schemaResult.schema) { - throw new AriesFrameworkError( - `Unable to resolve schema ${anonCredsCredential.credentialSchema.schema}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` - ) - } + credentialDefinitionResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialSchema.definition) + .getCredentialDefinition(agentContext, anonCredsCredential.credentialSchema.definition) + if (!credentialDefinitionResult.credentialDefinition) { + throw new AriesFrameworkError( + `Unable to resolve credential definition ${anonCredsCredential.credentialSchema.definition}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` + ) + } - // Resolve revocation registry if credential is revocable - let revocationRegistryResult: null | GetRevocationRegistryDefinitionReturn = null - if (anonCredsCredential.credentialStatus?.id) { - revocationRegistryResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialStatus?.id) - .getRevocationRegistryDefinition(agentContext, anonCredsCredential.credentialStatus?.id) + schemaResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialSchema.definition) + .getSchema(agentContext, anonCredsCredential.credentialSchema.schema) + if (!schemaResult.schema) { + throw new AriesFrameworkError( + `Unable to resolve schema ${anonCredsCredential.credentialSchema.schema}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + ) + } - if (!revocationRegistryResult.revocationRegistryDefinition) { + // Resolve revocation registry if credential is revocable + if (anonCredsCredential.credentialStatus?.id) { + revocationRegistryResult = await registryService + .getRegistryForIdentifier(agentContext, anonCredsCredential.credentialStatus?.id) + .getRevocationRegistryDefinition(agentContext, anonCredsCredential.credentialStatus?.id) + + if (!revocationRegistryResult.revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Unable to resolve revocation registry definition ${anonCredsCredential.credentialStatus?.id}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` + ) + } + } + } else { + const cred = attachment.getDataAsJson() + + credentialDefinitionResult = await registryService + .getRegistryForIdentifier(agentContext, cred.cred_def_id) + .getCredentialDefinition(agentContext, cred.cred_def_id) + if (!credentialDefinitionResult.credentialDefinition) { throw new AriesFrameworkError( - `Unable to resolve revocation registry definition ${anonCredsCredential.credentialStatus?.id}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` + `Unable to resolve credential definition ${cred.cred_def_id}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` ) } - } - // assert the credential values match the offer values - // const recordCredentialValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) - // assertCredentialValuesMatch(anonCredsCredential.values, recordCredentialValues) + schemaResult = await registryService + .getRegistryForIdentifier(agentContext, cred.cred_def_id) + .getSchema(agentContext, cred.schema_id) + if (!schemaResult.schema) { + throw new AriesFrameworkError( + `Unable to resolve schema ${cred.schema_id}: ${schemaResult.resolutionMetadata.error} ${schemaResult.resolutionMetadata.message}` + ) + } + + // Resolve revocation registry if credential is revocable + if (cred.rev_reg_id) { + revocationRegistryResult = await registryService + .getRegistryForIdentifier(agentContext, cred.rev_reg_id) + .getRevocationRegistryDefinition(agentContext, cred.rev_reg_id) + + if (!revocationRegistryResult.revocationRegistryDefinition) { + throw new AriesFrameworkError( + `Unable to resolve revocation registry definition ${cred.rev_reg_id}: ${revocationRegistryResult.resolutionMetadata.error} ${revocationRegistryResult.resolutionMetadata.message}` + ) + } + } + // assert the credential values match the offer values + const recordCredentialValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) + assertCredentialValuesMatch(cred.values, recordCredentialValues) + const credLegacy = Credential.fromJson(cred as unknown as JsonObject) + + const credential = W3CCredential.fromLegacy({ + credential: credLegacy, + credentialDefinition: credentialDefinitionResult.credentialDefinition as unknown as JsonObject, + }) + + anonCredsCredential = credential.toJson() as unknown as AnonCredsW3CCredential + } const credentialId = await anonCredsHolderService.storeCredential(agentContext, { credentialId: utils.uuid(), @@ -413,27 +476,23 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService }) // If the credential is revocable, store the revocation identifiers in the credential record - try { - if (anonCredsCredential.credentialStatus?.id) { - const credential = await anonCredsHolderService.getCredential(agentContext, { credentialId }) - - credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { - credentialRevocationId: credential.credentialRevocationId, - revocationRegistryId: credential.revocationRegistryId, - }) - credentialRecord.setTags({ - anonCredsRevocationRegistryId: credential.revocationRegistryId, - anonCredsCredentialRevocationId: credential.credentialRevocationId, - }) - } + if (revocationRegistryResult?.revocationRegistryDefinitionId) { + const credential = await anonCredsHolderService.getCredential(agentContext, { credentialId }) - credentialRecord.credentials.push({ - credentialRecordType: this.credentialRecordType, - credentialRecordId: credentialId, + credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { + credentialRevocationId: credential.credentialRevocationId, + revocationRegistryId: credential.revocationRegistryId, + }) + credentialRecord.setTags({ + anonCredsRevocationRegistryId: credential.revocationRegistryId, + anonCredsCredentialRevocationId: credential.credentialRevocationId, }) - } catch (_) { - /* empty */ } + + credentialRecord.credentials.push({ + credentialRecordType: this.credentialRecordType, + credentialRecordId: credentialId, + }) } public supportsFormat(format: string): boolean { diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 584fbd27d6..17685023f8 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -41,6 +41,7 @@ export interface AnonCredsCredentialRequest { blinded_ms: Record blinded_ms_correctness_proof: Record nonce: string + isW3C?: boolean } export type AnonCredsCredentialValues = Record diff --git a/packages/anoncreds/src/models/internal.ts b/packages/anoncreds/src/models/internal.ts index 8aacc72a52..9325062e53 100644 --- a/packages/anoncreds/src/models/internal.ts +++ b/packages/anoncreds/src/models/internal.ts @@ -40,4 +40,5 @@ export interface AnonCredsCredentialRequestMetadata { link_secret_blinding_data: AnonCredsLinkSecretBlindingData link_secret_name: string nonce: string + isW3C?: boolean } diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index bae0bd2e93..a7e69d776f 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -98,6 +98,7 @@ export interface CreateCredentialRequestOptions { credentialDefinition: AnonCredsCredentialDefinition linkSecretId?: string useLegacyProverDid?: boolean + isW3C?: boolean } export interface CreateCredentialRequestReturn { diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 4a0a6ac349..b49051f919 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -307,6 +307,7 @@ export class CredentialsApi implements Credent credentialFormats: options.credentialFormats, comment: options.comment, autoAcceptCredential: options.autoAcceptCredential, + isW3C: options.isW3C, }) const outboundMessageContext = await getOutboundMessageContext(this.agentContext, { diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 19e9f17295..61939ca33a 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -90,6 +90,7 @@ export interface AcceptCredentialOfferOptions, 'acceptOffer'> + isW3C?: boolean } /** diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index 9e438c6d1c..fa3a8e1fac 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -87,6 +87,7 @@ export interface CredentialFormatAcceptOfferOptions attachmentId?: string offerAttachment: Attachment + isW3C?: boolean } export interface CredentialFormatCreateOfferReturn extends CredentialFormatCreateReturn { diff --git a/packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts b/packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts index bbcbfcf3a1..eafc50da46 100644 --- a/packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts +++ b/packages/core/src/modules/credentials/protocol/CredentialProtocolOptions.ts @@ -123,6 +123,7 @@ export interface AcceptCredentialOfferOptions, 'acceptOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string + isW3C?: boolean } export interface NegotiateCredentialOfferOptions { diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index c8fe64e86d..0abd63b541 100644 --- a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts @@ -302,11 +302,13 @@ export class CredentialFormatCoordinator credentialFormats, formatServices, comment, + isW3C, }: { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload, 'acceptOffer'> formatServices: CredentialFormatService[] comment?: string + isW3C?: boolean } ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -331,6 +333,7 @@ export class CredentialFormatCoordinator offerAttachment, credentialRecord, credentialFormats, + isW3C, }) requestAttachments.push(attachment) diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts index 5d764fb5aa..f0a1309778 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts @@ -444,7 +444,7 @@ export class V2CredentialProtocol + { credentialRecord, autoAcceptCredential, comment, credentialFormats, isW3C }: AcceptCredentialOfferOptions ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -479,6 +479,7 @@ export class V2CredentialProtocol Date: Mon, 27 Nov 2023 12:01:59 +0500 Subject: [PATCH 7/8] refactoring: enable to issue in two formats while executing faber demo, remove specifying cred formats while sending cred request --- demo/src/AliceInquirer.ts | 4 +--- demo/src/BaseAgent.ts | 1 + demo/src/Faber.ts | 3 ++- demo/src/FaberInquirer.ts | 4 +++- .../src/services/AnonCredsRsHolderService.ts | 2 -- .../src/services/AnonCredsRsIssuerService.ts | 2 +- .../src/formats/AnonCredsCredentialFormatService.ts | 11 +++++------ packages/anoncreds/src/models/exchange.ts | 1 - packages/anoncreds/src/models/internal.ts | 1 - .../src/services/AnonCredsIssuerServiceOptions.ts | 1 + .../core/src/modules/credentials/CredentialsApi.ts | 2 +- .../src/modules/credentials/CredentialsApiOptions.ts | 2 +- .../formats/CredentialFormatServiceOptions.ts | 2 ++ .../credentials/protocol/CredentialProtocolOptions.ts | 2 +- .../protocol/v2/CredentialFormatCoordinator.ts | 11 ++++++++--- .../credentials/protocol/v2/V2CredentialProtocol.ts | 7 ++++--- .../protocol/v2/messages/V2OfferCredentialMessage.ts | 9 ++++++++- 17 files changed, 39 insertions(+), 26 deletions(-) diff --git a/demo/src/AliceInquirer.ts b/demo/src/AliceInquirer.ts index 7400a1c6bd..a665d5e80b 100644 --- a/demo/src/AliceInquirer.ts +++ b/demo/src/AliceInquirer.ts @@ -74,9 +74,7 @@ export class AliceInquirer extends BaseInquirer { if (confirm.options === ConfirmOptions.No) { await this.alice.agent.credentials.declineOffer(credentialRecord.id) } else if (confirm.options === ConfirmOptions.Yes) { - const confirm = await prompt([this.inquireConfirmation('Do you want to get credential in W3C format')]) - - await this.alice.acceptCredentialOffer(credentialRecord, confirm.options == ConfirmOptions.Yes) + await this.alice.acceptCredentialOffer(credentialRecord) } } diff --git a/demo/src/BaseAgent.ts b/demo/src/BaseAgent.ts index c2e787e32a..5b2a6d3ac1 100644 --- a/demo/src/BaseAgent.ts +++ b/demo/src/BaseAgent.ts @@ -85,6 +85,7 @@ export class BaseAgent { key: name, }, endpoints: [`http://localhost:${this.port}`], + // logger: new ConsoleLogger(LogLevel.trace), } satisfies InitConfig this.config = config diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index b48f5b4d11..9585ad1f1e 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -195,7 +195,7 @@ export class Faber extends BaseAgent { return this.credentialDefinition } - public async issueCredential() { + public async issueCredential(isW3C?: boolean) { const schema = await this.registerSchema() const credentialDefinition = await this.registerCredentialDefinition(schema.schemaId) const connectionRecord = await this.getConnectionRecord() @@ -224,6 +224,7 @@ export class Faber extends BaseAgent { credentialDefinitionId: credentialDefinition.credentialDefinitionId, }, }, + isW3C, }) this.ui.updateBottomBar( `\nCredential offer sent!\n\nGo to the Alice agent to accept the credential offer\n\n${Color.Reset}` diff --git a/demo/src/FaberInquirer.ts b/demo/src/FaberInquirer.ts index 87dd55b96c..f23cb8381b 100644 --- a/demo/src/FaberInquirer.ts +++ b/demo/src/FaberInquirer.ts @@ -91,7 +91,9 @@ export class FaberInquirer extends BaseInquirer { public async credential() { const registry = await prompt([this.inquireOptions([RegistryOptions.indy, RegistryOptions.cheqd])]) await this.faber.importDid(registry.options) - await this.faber.issueCredential() + const confirm = await prompt([this.inquireConfirmation('Do you want to issue credential in W3C format')]) + + await this.faber.issueCredential(confirm.options == ConfirmOptions.Yes) const title = 'Is the credential offer accepted?' await this.listener.newAcceptedPrompt(title, this) } diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index 53314369c2..b82f042ba0 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -27,7 +27,6 @@ import type { CredentialRequestMetadata, JsonObject, W3CCredentialEntry, - Credential, } from '@hyperledger/anoncreds-shared' import { @@ -397,7 +396,6 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { }) } const credRequest = createReturnObj.credentialRequest.toJson() as unknown as AnonCredsCredentialRequest - credRequest.isW3C = options.isW3C return { credentialRequest: credRequest, diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts index 35f54fed27..375be2969b 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts @@ -138,7 +138,7 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { agentContext: AgentContext, options: CreateCredentialOptions ): Promise { - if (options.credentialRequest.isW3C) { + if (options.isW3C) { return this.createCredentialW3c(agentContext, options) } else { return this.createCredentialLegacy(agentContext, options) diff --git a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts index 35d4ef2bd1..685533ba2f 100644 --- a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts @@ -49,7 +49,6 @@ import { JsonTransformer, } from '@aries-framework/core' import { Credential, W3CCredential } from '@hyperledger/anoncreds-shared' -import { Schema } from '@hyperledger/anoncreds-shared/api/Schema' import { AnonCredsError } from '../error' import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' @@ -254,11 +253,9 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService credentialOffer, credentialDefinition, linkSecretId: credentialFormats?.anoncreds?.linkSecretId, - isW3C: isW3C, + isW3C, }) - credentialRequestMetadata.isW3C = isW3C - credentialRecord.metadata.set( AnonCredsCredentialRequestMetadataKey, credentialRequestMetadata @@ -299,6 +296,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService attachmentId, offerAttachment, requestAttachment, + isW3C, }: CredentialFormatAcceptRequestOptions ): Promise { // Assert credential attributes @@ -322,6 +320,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService credentialOffer, credentialRequest, credentialValues: convertAttributesToCredentialValues(credentialAttributes), + isW3C, }) if (credential.rev_reg_id) { @@ -351,7 +350,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService */ public async processCredential( agentContext: AgentContext, - { credentialRecord, attachment }: CredentialFormatProcessCredentialOptions + { credentialRecord, attachment, isW3C }: CredentialFormatProcessCredentialOptions ): Promise { const credentialRequestMetadata = credentialRecord.metadata.get( AnonCredsCredentialRequestMetadataKey @@ -381,7 +380,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService console.log(attachment.getDataAsJson()) console.log('------------------------------------------------------\n') - if (credentialRequestMetadata.isW3C) { + if (isW3C) { anonCredsCredential = attachment.getDataAsJson() credentialDefinitionResult = await registryService diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 17685023f8..584fbd27d6 100644 --- a/packages/anoncreds/src/models/exchange.ts +++ b/packages/anoncreds/src/models/exchange.ts @@ -41,7 +41,6 @@ export interface AnonCredsCredentialRequest { blinded_ms: Record blinded_ms_correctness_proof: Record nonce: string - isW3C?: boolean } export type AnonCredsCredentialValues = Record diff --git a/packages/anoncreds/src/models/internal.ts b/packages/anoncreds/src/models/internal.ts index 9325062e53..8aacc72a52 100644 --- a/packages/anoncreds/src/models/internal.ts +++ b/packages/anoncreds/src/models/internal.ts @@ -40,5 +40,4 @@ export interface AnonCredsCredentialRequestMetadata { link_secret_blinding_data: AnonCredsLinkSecretBlindingData link_secret_name: string nonce: string - isW3C?: boolean } diff --git a/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts index c7da246b9b..602eb76b3a 100644 --- a/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsIssuerServiceOptions.ts @@ -33,6 +33,7 @@ export interface CreateCredentialOptions { revocationRegistryId?: string // TODO: should this just be the tails file instead of a path? tailsFilePath?: string + isW3C?: boolean } export interface CreateCredentialReturn { diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index b49051f919..aaaae68bee 100644 --- a/packages/core/src/modules/credentials/CredentialsApi.ts +++ b/packages/core/src/modules/credentials/CredentialsApi.ts @@ -265,6 +265,7 @@ export class CredentialsApi implements Credent autoAcceptCredential: options.autoAcceptCredential, comment: options.comment, connectionRecord, + isW3C: options.isW3C, }) this.logger.debug('Offer Message successfully created; message= ', message) @@ -307,7 +308,6 @@ export class CredentialsApi implements Credent credentialFormats: options.credentialFormats, comment: options.comment, autoAcceptCredential: options.autoAcceptCredential, - isW3C: options.isW3C, }) const outboundMessageContext = await getOutboundMessageContext(this.agentContext, { diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 61939ca33a..bbc85ccdf4 100644 --- a/packages/core/src/modules/credentials/CredentialsApiOptions.ts +++ b/packages/core/src/modules/credentials/CredentialsApiOptions.ts @@ -70,6 +70,7 @@ export interface CreateCredentialOfferOptions credentialFormats: CredentialFormatPayload, 'createOffer'> + isW3C?: boolean } /** @@ -90,7 +91,6 @@ export interface AcceptCredentialOfferOptions, 'acceptOffer'> - isW3C?: boolean } /** diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index fa3a8e1fac..c30e500a46 100644 --- a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts +++ b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts @@ -55,6 +55,7 @@ export interface CredentialFormatProcessOptions { export interface CredentialFormatProcessCredentialOptions extends CredentialFormatProcessOptions { requestAttachment: Attachment + isW3C?: boolean } export interface CredentialFormatCreateProposalOptions { @@ -106,6 +107,7 @@ export interface CredentialFormatAcceptRequestOptions, 'createOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string + isW3C?: boolean } export interface AcceptCredentialOfferOptions { @@ -123,7 +124,6 @@ export interface AcceptCredentialOfferOptions, 'acceptOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string - isW3C?: boolean } export interface NegotiateCredentialOfferOptions { diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index 0abd63b541..7e6d8865ff 100644 --- a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts +++ b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts @@ -207,11 +207,13 @@ export class CredentialFormatCoordinator formatServices, credentialRecord, comment, + isW3C, }: { formatServices: CredentialFormatService[] credentialFormats: CredentialFormatPayload, 'createOffer'> credentialRecord: CredentialExchangeRecord comment?: string + isW3C?: boolean } ): Promise { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -252,6 +254,7 @@ export class CredentialFormatCoordinator comment, offerAttachments, credentialPreview, + isW3C, }) message.setThread({ threadId: credentialRecord.threadId, parentThreadId: credentialRecord.parentThreadId }) @@ -302,13 +305,11 @@ export class CredentialFormatCoordinator credentialFormats, formatServices, comment, - isW3C, }: { credentialRecord: CredentialExchangeRecord credentialFormats?: CredentialFormatPayload, 'acceptOffer'> formatServices: CredentialFormatService[] comment?: string - isW3C?: boolean } ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -333,7 +334,7 @@ export class CredentialFormatCoordinator offerAttachment, credentialRecord, credentialFormats, - isW3C, + isW3C: offerMessage.isW3C, }) requestAttachments.push(attachment) @@ -489,6 +490,7 @@ export class CredentialFormatCoordinator offerAttachment, credentialRecord, credentialFormats, + isW3C: offerMessage?.isW3C, }) credentialAttachments.push(attachment) @@ -520,11 +522,13 @@ export class CredentialFormatCoordinator message, requestMessage, formatServices, + isW3C, }: { credentialRecord: CredentialExchangeRecord message: V2IssueCredentialMessage requestMessage: V2RequestCredentialMessage formatServices: CredentialFormatService[] + isW3C?: boolean } ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -541,6 +545,7 @@ export class CredentialFormatCoordinator attachment, requestAttachment, credentialRecord, + isW3C, }) } diff --git a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts index f0a1309778..cb443aa1a9 100644 --- a/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts +++ b/packages/core/src/modules/credentials/protocol/v2/V2CredentialProtocol.ts @@ -326,7 +326,7 @@ export class V2CredentialProtocol + { credentialFormats, autoAcceptCredential, comment, connectionRecord, isW3C }: CreateCredentialOfferOptions ): Promise> { const credentialRepository = agentContext.dependencyManager.resolve(CredentialRepository) @@ -348,6 +348,7 @@ export class V2CredentialProtocol + { credentialRecord, autoAcceptCredential, comment, credentialFormats }: AcceptCredentialOfferOptions ) { const didCommMessageRepository = agentContext.dependencyManager.resolve(DidCommMessageRepository) @@ -479,7 +480,6 @@ export class V2CredentialProtocol attachment.id === id) } From 5daaf4647bd03b2d16ec96517558b5496121068e Mon Sep 17 00:00:00 2001 From: "artem.ivanov" Date: Mon, 27 Nov 2023 18:46:10 +0300 Subject: [PATCH 8/8] Services refactoring Signed-off-by: Artemkaaas --- demo/package.json | 2 +- demo/src/Alice.ts | 2 +- demo/src/Faber.ts | 2 +- packages/anoncreds-rs/package.json | 4 +- .../src/services/AnonCredsRsHolderService.ts | 268 ++++++++++++------ .../AnonCredsCredentialFormatService.ts | 52 ++-- .../formats/AnonCredsProofFormatService.ts | 24 +- .../LegacyIndyCredentialFormatService.ts | 8 +- .../src/services/AnonCredsHolderService.ts | 8 + .../services/AnonCredsHolderServiceOptions.ts | 16 +- .../services/IndySdkHolderService.ts | 12 +- yarn.lock | 25 +- 12 files changed, 271 insertions(+), 152 deletions(-) diff --git a/demo/package.json b/demo/package.json index ae46d4d6ab..7988b23d87 100644 --- a/demo/package.json +++ b/demo/package.json @@ -15,7 +15,7 @@ }, "dependencies": { "@hyperledger/indy-vdr-nodejs": "^0.2.0-dev.5", - "@hyperledger/anoncreds-nodejs": "file:/Users/abdulbois/Documents/anoncreds-rs/wrappers/javascript/packages/anoncreds-nodejs/build", + "@hyperledger/anoncreds-nodejs": "file:/Users/artem/anoncreds-rs/wrappers/javascript/packages/anoncreds-nodejs/build", "@hyperledger/aries-askar-nodejs": "^0.2.0-dev.1", "inquirer": "^8.2.5" }, diff --git a/demo/src/Alice.ts b/demo/src/Alice.ts index 2de378d8c1..bf3adbc89d 100644 --- a/demo/src/Alice.ts +++ b/demo/src/Alice.ts @@ -13,7 +13,7 @@ export class Alice extends BaseAgent { } public static async build(): Promise { - const alice = new Alice(9000, 'alice') + const alice = new Alice(9000, 'alice1') await alice.initializeAgent() return alice } diff --git a/demo/src/Faber.ts b/demo/src/Faber.ts index 9585ad1f1e..ff31e1f111 100644 --- a/demo/src/Faber.ts +++ b/demo/src/Faber.ts @@ -29,7 +29,7 @@ export class Faber extends BaseAgent { } public static async build(): Promise { - const faber = new Faber(9001, 'faber11') + const faber = new Faber(9001, 'faber1') await faber.initializeAgent() return faber } diff --git a/packages/anoncreds-rs/package.json b/packages/anoncreds-rs/package.json index 37a2c58eb6..b65ca02533 100644 --- a/packages/anoncreds-rs/package.json +++ b/packages/anoncreds-rs/package.json @@ -32,8 +32,8 @@ "tsyringe": "^4.8.0" }, "devDependencies": { - "@hyperledger/anoncreds-nodejs": "file:///Users/abdulbois/Documents/anoncreds-rs/wrappers/javascript/packages/anoncreds-nodejs/build", - "@hyperledger/anoncreds-shared": "file:///Users/abdulbois/Documents/anoncreds-rs/wrappers/javascript/packages/anoncreds-shared/build", + "@hyperledger/anoncreds-nodejs": "file:/Users/artem/anoncreds-rs/wrappers/javascript/packages/anoncreds-nodejs/build", + "@hyperledger/anoncreds-shared": "file:/Users/artem/anoncreds-rs/wrappers/javascript/packages/anoncreds-shared/build", "@types/ref-array-di": "^1.2.6", "@types/ref-struct-di": "^1.1.10", "reflect-metadata": "^0.1.13", diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts index b82f042ba0..a937c66834 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -19,6 +19,7 @@ import type { GetCredentialsForProofRequestReturn, GetCredentialsOptions, StoreCredentialOptions, + StoreW3CCredentialOptions, } from '@aries-framework/anoncreds' import type { AgentContext, Query, SimpleQuery } from '@aries-framework/core' import type { CredentialEntry } from '@hyperledger/anoncreds-nodejs' @@ -44,6 +45,7 @@ import { AriesFrameworkError, injectable, JsonTransformer, TypedArrayEncoder, ut import { CredentialRequest } from '@hyperledger/anoncreds-nodejs' import { anoncreds, + Credential, CredentialRevocationState, LinkSecret, Presentation, @@ -71,17 +73,9 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } public async createProof(agentContext: AgentContext, options: CreateProofOptions): Promise { - const { proofRequest } = options - if (proofRequest.isW3C) { - return this.createProofW3C(agentContext, options) - } else { - return this.createProofLegacy(agentContext, options) - } - } - public async createProofW3C(agentContext: AgentContext, options: CreateProofOptions): Promise { const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options - let presentation: W3CPresentation | undefined + let presentation: Presentation | undefined try { const rsCredentialDefinitions: Record = {} for (const credDefId in credentialDefinitions) { @@ -92,22 +86,21 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { for (const schemaId in schemas) { rsSchemas[schemaId] = schemas[schemaId] as unknown as JsonObject } - const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) // Cache retrieved credentials in order to minimize storage calls - const retrievedCredentials = new Map() + const retrievedCredentials = new Map() const credentialEntryFromAttribute = async ( attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch - ): Promise<{ linkSecretId: string; credentialEntry: W3CCredentialEntry }> => { + ): Promise<{ linkSecretId: string; credentialEntry: CredentialEntry }> => { let credentialRecord = retrievedCredentials.get(attribute.credentialId) if (!credentialRecord) { credentialRecord = await credentialRepository.getByCredentialId(agentContext, attribute.credentialId) retrievedCredentials.set(attribute.credentialId, credentialRecord) } - // @ts-ignore - const revocationRegistryDefinitionId = credentialRecord.credential.credentialStatus?.id + const revocationRegistryDefinitionId = credentialRecord.credential.rev_reg_id const revocationRegistryIndex = credentialRecord.credentialRevocationId // TODO: Check if credential has a revocation registry id (check response from anoncreds-rs API, as it is @@ -156,7 +149,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } const credentialsProve: CredentialProve[] = [] - const credentials: { linkSecretId: string; credentialEntry: W3CCredentialEntry }[] = [] + const credentials: { linkSecretId: string; credentialEntry: CredentialEntry }[] = [] let entryIndex = 0 for (const referent in selectedCredentials.attributes) { @@ -187,16 +180,16 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { throw new AnonCredsRsError('Link Secret value not stored') } - presentation = W3CPresentation.create({ + presentation = Presentation.create({ credentialDefinitions: rsCredentialDefinitions, schemas: rsSchemas, presentationRequest: proofRequest as unknown as JsonObject, - credentials: credentials.map((entry) => entry.credentialEntry), + credentials: credentials.map((entry) => entry.credentialEntry as CredentialEntry), credentialsProve, - // selfAttest: selectedCredentials.selfAttestedAttributes, + selfAttest: selectedCredentials.selfAttestedAttributes, linkSecret: linkSecretRecord.value, }) - console.log('\n------------Created presentation in W3C format------------') + console.log('\n------------Created presentation in Old format------------') console.dir(presentation.toJson(), { depth: null }) console.log('------------------------------------------------------\n') @@ -205,10 +198,91 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { presentation?.handle.clear() } } - public async createProofLegacy(agentContext: AgentContext, options: CreateProofOptions): Promise { + + public async createCredentialRequest( + agentContext: AgentContext, + options: CreateCredentialRequestOptions + ): Promise { + const { useLegacyProverDid, credentialDefinition, credentialOffer } = options + let createReturnObj: + | { + credentialRequest: W3CCredentialRequest | CredentialRequest + credentialRequestMetadata: CredentialRequestMetadata + } + | undefined + try { + const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + + // If a link secret is specified, use it. Otherwise, attempt to use default link secret + let linkSecretRecord = options.linkSecretId + ? await linkSecretRepository.getByLinkSecretId(agentContext, options.linkSecretId) + : await linkSecretRepository.findDefault(agentContext) + + // No default link secret. Automatically create one if set on module config + if (!linkSecretRecord) { + const moduleConfig = agentContext.dependencyManager.resolve(AnonCredsRsModuleConfig) + if (!moduleConfig.autoCreateLinkSecret) { + throw new AnonCredsRsError( + 'No link secret provided to createCredentialRequest and no default link secret has been found' + ) + } + const { linkSecretId, linkSecretValue } = await this.createLinkSecret(agentContext, {}) + linkSecretRecord = await storeLinkSecret(agentContext, { + linkSecretId, + linkSecretValue, + setAsDefault: true, + }) + } + + if (!linkSecretRecord.value) { + throw new AnonCredsRsError('Link Secret value not stored') + } + + const isLegacyIdentifier = credentialOffer.cred_def_id.match(unqualifiedCredentialDefinitionIdRegex) + if (!isLegacyIdentifier && useLegacyProverDid) { + throw new AriesFrameworkError('Cannot use legacy prover_did with non-legacy identifiers') + } + + if (options.isW3C) { + createReturnObj = W3CCredentialRequest.create({ + 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, + linkSecret: linkSecretRecord.value, + linkSecretId: linkSecretRecord.linkSecretId, + }) + } else { + createReturnObj = CredentialRequest.create({ + 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, + linkSecret: linkSecretRecord.value, + linkSecretId: linkSecretRecord.linkSecretId, + }) + } + const credRequest = createReturnObj.credentialRequest.toJson() as unknown as AnonCredsCredentialRequest + + return { + credentialRequest: credRequest, + credentialRequestMetadata: + createReturnObj.credentialRequestMetadata.toJson() as unknown as AnonCredsCredentialRequestMetadata, + } + } finally { + createReturnObj?.credentialRequest.handle.clear() + createReturnObj?.credentialRequestMetadata.handle.clear() + } + } + + public async createW3CProof(agentContext: AgentContext, options: CreateProofOptions): Promise { const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options - let presentation: Presentation | undefined + let presentation: W3CPresentation | undefined try { const rsCredentialDefinitions: Record = {} for (const credDefId in credentialDefinitions) { @@ -219,21 +293,22 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { for (const schemaId in schemas) { rsSchemas[schemaId] = schemas[schemaId] as unknown as JsonObject } - const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) // Cache retrieved credentials in order to minimize storage calls - const retrievedCredentials = new Map() + const retrievedCredentials = new Map() const credentialEntryFromAttribute = async ( attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch - ): Promise<{ linkSecretId: string; credentialEntry: CredentialEntry }> => { + ): Promise<{ linkSecretId: string; credentialEntry: W3CCredentialEntry }> => { let credentialRecord = retrievedCredentials.get(attribute.credentialId) if (!credentialRecord) { credentialRecord = await credentialRepository.getByCredentialId(agentContext, attribute.credentialId) retrievedCredentials.set(attribute.credentialId, credentialRecord) } - const revocationRegistryDefinitionId = credentialRecord.credential.rev_reg_id + // @ts-ignore + const revocationRegistryDefinitionId = credentialRecord.credential.credentialStatus?.id const revocationRegistryIndex = credentialRecord.credentialRevocationId // TODO: Check if credential has a revocation registry id (check response from anoncreds-rs API, as it is @@ -282,7 +357,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } const credentialsProve: CredentialProve[] = [] - const credentials: { linkSecretId: string; credentialEntry: CredentialEntry }[] = [] + const credentials: { linkSecretId: string; credentialEntry: W3CCredentialEntry }[] = [] let entryIndex = 0 for (const referent in selectedCredentials.attributes) { @@ -313,16 +388,16 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { throw new AnonCredsRsError('Link Secret value not stored') } - presentation = Presentation.create({ + presentation = W3CPresentation.create({ credentialDefinitions: rsCredentialDefinitions, schemas: rsSchemas, presentationRequest: proofRequest as unknown as JsonObject, - credentials: credentials.map((entry) => entry.credentialEntry as CredentialEntry), + credentials: credentials.map((entry) => entry.credentialEntry), credentialsProve, - selfAttest: selectedCredentials.selfAttestedAttributes, + // selfAttest: selectedCredentials.selfAttestedAttributes, linkSecret: linkSecretRecord.value, }) - console.log('\n------------Created presentation in Old format------------') + console.log('\n------------Created presentation in W3C format------------') console.dir(presentation.toJson(), { depth: null }) console.log('------------------------------------------------------\n') @@ -332,83 +407,88 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } } - public async createCredentialRequest( - agentContext: AgentContext, - options: CreateCredentialRequestOptions - ): Promise { - const { useLegacyProverDid, credentialDefinition, credentialOffer } = options - let createReturnObj: - | { - credentialRequest: W3CCredentialRequest | CredentialRequest - credentialRequestMetadata: CredentialRequestMetadata - } - | undefined - try { - const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) + public async storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { + const { credential, credentialDefinition, credentialRequestMetadata, revocationRegistry, schema } = options - // If a link secret is specified, use it. Otherwise, attempt to use default link secret - let linkSecretRecord = options.linkSecretId - ? await linkSecretRepository.getByLinkSecretId(agentContext, options.linkSecretId) - : await linkSecretRepository.findDefault(agentContext) + const linkSecretRecord = await agentContext.dependencyManager + .resolve(AnonCredsLinkSecretRepository) + .getByLinkSecretId(agentContext, credentialRequestMetadata.link_secret_name) - // No default link secret. Automatically create one if set on module config - if (!linkSecretRecord) { - const moduleConfig = agentContext.dependencyManager.resolve(AnonCredsRsModuleConfig) - if (!moduleConfig.autoCreateLinkSecret) { - throw new AnonCredsRsError( - 'No link secret provided to createCredentialRequest and no default link secret has been found' - ) - } - const { linkSecretId, linkSecretValue } = await this.createLinkSecret(agentContext, {}) - linkSecretRecord = await storeLinkSecret(agentContext, { linkSecretId, linkSecretValue, setAsDefault: true }) - } + if (!linkSecretRecord.value) { + throw new AnonCredsRsError('Link Secret value not stored') + } - if (!linkSecretRecord.value) { - throw new AnonCredsRsError('Link Secret value not stored') - } + const revocationRegistryDefinition = revocationRegistry?.definition as unknown as JsonObject - const isLegacyIdentifier = credentialOffer.cred_def_id.match(unqualifiedCredentialDefinitionIdRegex) - if (!isLegacyIdentifier && useLegacyProverDid) { - throw new AriesFrameworkError('Cannot use legacy prover_did with non-legacy identifiers') - } + const credentialId = options.credentialId ?? utils.uuid() - if (options.isW3C) { - createReturnObj = W3CCredentialRequest.create({ - 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, - linkSecret: linkSecretRecord.value, + let credentialObj: Credential | undefined + let processedCredential: Credential | undefined + try { + credentialObj = Credential.fromJson(credential as unknown as JsonObject) + processedCredential = credentialObj.process({ + credentialDefinition: credentialDefinition as unknown as JsonObject, + credentialRequestMetadata: credentialRequestMetadata as unknown as JsonObject, + linkSecret: linkSecretRecord.value, + revocationRegistryDefinition, + }) + + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + + const methodName = agentContext.dependencyManager + .resolve(AnonCredsRegistryService) + .getRegistryForIdentifier(agentContext, credential.cred_def_id).methodName + + await credentialRepository.save( + agentContext, + new AnonCredsCredentialRecord({ + credential: processedCredential.toJson() as unknown as AnonCredsCredential, + credentialId, linkSecretId: linkSecretRecord.linkSecretId, + issuerId: options.credentialDefinition.issuerId, + schemaName: schema.name, + schemaIssuerId: schema.issuerId, + schemaVersion: schema.version, + credentialRevocationId: processedCredential.revocationRegistryIndex?.toString(), + methodName, }) - } else { - createReturnObj = CredentialRequest.create({ - 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, - linkSecret: linkSecretRecord.value, + ) + + console.log('\n------------Saved credential in Legacy format------------') + console.log(processedCredential.toJson()) + console.log('------------------------------------------------------\n') + + const w3cCredentialRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) + const w3cCredential = processedCredential + .toW3C({ credentialDefinition: credentialDefinition as unknown as JsonObject }) + .toJson() as unknown as AnonCredsW3CCredential + await w3cCredentialRepository.save( + agentContext, + new AnonCredsW3CCredentialRecord({ + credential: w3cCredential, + credentialId: options.credentialId ?? utils.uuid(), linkSecretId: linkSecretRecord.linkSecretId, + issuerId: options.credentialDefinition.issuerId, + schemaName: schema.name, + schemaIssuerId: schema.issuerId, + schemaVersion: schema.version, + credentialRevocationId: processedCredential.revocationRegistryIndex?.toString(), + methodName, }) - } - const credRequest = createReturnObj.credentialRequest.toJson() as unknown as AnonCredsCredentialRequest + ) - return { - credentialRequest: credRequest, - credentialRequestMetadata: - createReturnObj.credentialRequestMetadata.toJson() as unknown as AnonCredsCredentialRequestMetadata, - } + console.log('\n------------Saved credential in W3C format------------') + console.log(w3cCredential) + console.log('------------------------------------------------------\n') + + return credentialId } finally { - createReturnObj?.credentialRequest.handle.clear() - createReturnObj?.credentialRequestMetadata.handle.clear() + credentialObj?.handle.clear() + processedCredential?.handle.clear() } } - public async storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { + public async storeW3CCredential(agentContext: AgentContext, options: StoreW3CCredentialOptions): Promise { const { credential, credentialDefinition, credentialRequestMetadata, revocationRegistry, schema } = options const linkSecretRecord = await agentContext.dependencyManager @@ -509,6 +589,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { return credInfo } + private createAnonCredsCredentialInfoW3C(credentialRecord: AnonCredsW3CCredentialRecord): AnonCredsCredentialInfo { const cred = credentialRecord.credential const credId = credentialRecord.credentialId @@ -532,6 +613,7 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { methodName: methodName, } } + private createAnonCredsCredentialInfo(credentialRecord: AnonCredsCredentialRecord): AnonCredsCredentialInfo { const credId = credentialRecord.credentialId const methodName = credentialRecord.methodName diff --git a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts index 685533ba2f..853eac3b1c 100644 --- a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts @@ -49,6 +49,7 @@ import { JsonTransformer, } from '@aries-framework/core' import { Credential, W3CCredential } from '@hyperledger/anoncreds-shared' +import console from 'console' import { AnonCredsError } from '../error' import { AnonCredsCredentialProposal } from '../models/AnonCredsCredentialProposal' @@ -62,7 +63,6 @@ import { createAndLinkAttachmentsToPreview, } from '../utils/credential' import { AnonCredsCredentialMetadataKey, AnonCredsCredentialRequestMetadataKey } from '../utils/metadata' -import console from 'console' const ANONCREDS_CREDENTIAL_OFFER = 'anoncreds/credential-offer@v1.0' const ANONCREDS_CREDENTIAL_REQUEST = 'anoncreds/credential-request@v1.0' @@ -375,6 +375,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService let schemaResult: GetSchemaReturn let revocationRegistryResult: null | GetRevocationRegistryDefinitionReturn = null let anonCredsCredential: AnonCredsW3CCredential + let credentialId: string console.log('\n------------Processing an incoming credential------------') console.log(attachment.getDataAsJson()) @@ -413,6 +414,21 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService ) } } + + credentialId = await anonCredsHolderService.storeW3CCredential(agentContext, { + credentialId: utils.uuid(), + credentialRequestMetadata, + credential: anonCredsCredential, + credentialDefinitionId: credentialDefinitionResult.credentialDefinitionId, + credentialDefinition: credentialDefinitionResult.credentialDefinition, + schema: schemaResult.schema, + revocationRegistry: revocationRegistryResult?.revocationRegistryDefinition + ? { + definition: revocationRegistryResult.revocationRegistryDefinition, + id: revocationRegistryResult.revocationRegistryDefinitionId, + } + : undefined, + }) } else { const cred = attachment.getDataAsJson() @@ -449,31 +465,23 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService // assert the credential values match the offer values const recordCredentialValues = convertAttributesToCredentialValues(credentialRecord.credentialAttributes) assertCredentialValuesMatch(cred.values, recordCredentialValues) - const credLegacy = Credential.fromJson(cred as unknown as JsonObject) - const credential = W3CCredential.fromLegacy({ - credential: credLegacy, - credentialDefinition: credentialDefinitionResult.credentialDefinition as unknown as JsonObject, + credentialId = await anonCredsHolderService.storeCredential(agentContext, { + credentialId: utils.uuid(), + credentialRequestMetadata, + credential: cred, + credentialDefinitionId: credentialDefinitionResult.credentialDefinitionId, + credentialDefinition: credentialDefinitionResult.credentialDefinition, + schema: schemaResult.schema, + revocationRegistry: revocationRegistryResult?.revocationRegistryDefinition + ? { + definition: revocationRegistryResult.revocationRegistryDefinition, + id: revocationRegistryResult.revocationRegistryDefinitionId, + } + : undefined, }) - - anonCredsCredential = credential.toJson() as unknown as AnonCredsW3CCredential } - const credentialId = await anonCredsHolderService.storeCredential(agentContext, { - credentialId: utils.uuid(), - credentialRequestMetadata, - credential: anonCredsCredential, - credentialDefinitionId: credentialDefinitionResult.credentialDefinitionId, - credentialDefinition: credentialDefinitionResult.credentialDefinition, - schema: schemaResult.schema, - revocationRegistry: revocationRegistryResult?.revocationRegistryDefinition - ? { - definition: revocationRegistryResult.revocationRegistryDefinition, - id: revocationRegistryResult.revocationRegistryDefinitionId, - } - : undefined, - }) - // If the credential is revocable, store the revocation identifiers in the credential record if (revocationRegistryResult?.revocationRegistryDefinitionId) { const credential = await anonCredsHolderService.getCredential(agentContext, { credentialId }) diff --git a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts index af31b54474..fa09791b75 100644 --- a/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsProofFormatService.ts @@ -621,13 +621,23 @@ export class AnonCredsProofFormatService implements ProofFormatService createProof(agentContext: AgentContext, options: CreateProofOptions): Promise + createW3CProof(agentContext: AgentContext, options: CreateProofOptions): Promise + storeCredential( agentContext: AgentContext, options: StoreCredentialOptions, metadata?: Record ): Promise + storeW3CCredential( + agentContext: AgentContext, + options: StoreW3CCredentialOptions, + metadata?: Record + ): Promise // TODO: this doesn't actually return the credential, as the indy-sdk doesn't support that // We could come up with a hack (as we've received the credential at one point), but for diff --git a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts index a7e69d776f..45f3e6e374 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -1,4 +1,5 @@ import type { + AnonCredsCredential, AnonCredsCredentialInfo, AnonCredsCredentialRequestMetadata, AnonCredsSelectedCredentials, @@ -17,8 +18,6 @@ import type { AnonCredsSchema, } from '../models/registry' -import { W3CCredential } from '@hyperledger/anoncreds-shared' - export interface AnonCredsAttributeInfo { name?: string names?: string[] @@ -46,6 +45,19 @@ export interface CreateProofOptions { } export interface StoreCredentialOptions { + credentialRequestMetadata: AnonCredsCredentialRequestMetadata + credential: AnonCredsCredential + credentialDefinition: AnonCredsCredentialDefinition + schema: AnonCredsSchema + credentialDefinitionId: string + credentialId?: string + revocationRegistry?: { + id: string + definition: AnonCredsRevocationRegistryDefinition + } +} + +export interface StoreW3CCredentialOptions { credentialRequestMetadata: AnonCredsCredentialRequestMetadata credential: AnonCredsW3CCredential credentialDefinition: AnonCredsCredentialDefinition diff --git a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts index 07f7f40fed..b9de778894 100644 --- a/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts +++ b/packages/indy-sdk/src/anoncreds/services/IndySdkHolderService.ts @@ -12,7 +12,9 @@ import type { AnonCredsSelectedCredentials, CreateLinkSecretOptions, CreateLinkSecretReturn, - GetCredentialsOptions, AnonCredsCredential, + GetCredentialsOptions, + AnonCredsCredential, + StoreW3CCredentialOptions, } from '@aries-framework/anoncreds' import type { AgentContext } from '@aries-framework/core' import type { @@ -169,6 +171,10 @@ export class IndySdkHolderService implements AnonCredsHolderService { } } + public async createW3CProof(agentContext: AgentContext, options: CreateProofOptions): Promise { + throw new AriesFrameworkError('Not supported') + } + public async storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { assertIndySdkWallet(agentContext.wallet) assertAllUnqualified({ @@ -211,6 +217,10 @@ export class IndySdkHolderService implements AnonCredsHolderService { } } + public async storeW3CCredential(agentContext: AgentContext, options: StoreW3CCredentialOptions): Promise { + throw new AriesFrameworkError('Not supported') + } + public async getCredential( agentContext: AgentContext, options: GetCredentialOptions diff --git a/yarn.lock b/yarn.lock index 2afc180e9f..91ca36f36e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1205,22 +1205,11 @@ 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.2.0-dev.4": - version "0.2.0-dev.4" - resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-nodejs/-/anoncreds-nodejs-0.2.0-dev.4.tgz#ac125817beb631dedbe27cb8d4c21d2123104d5e" - integrity sha512-EH/jAH+aATH9KByWF1lk1p76BN6VIsRZhG7jyRT1LAaaUNnmpQnjX6d/Mfkofvk4xFIRbp0cDl/UjaKaKfLsww== - dependencies: - "@2060.io/ffi-napi" "4.0.8" - "@2060.io/ref-napi" "3.0.6" - "@hyperledger/anoncreds-shared" "0.2.0-dev.4" - "@mapbox/node-pre-gyp" "^1.0.11" - ref-array-di "1.2.2" - ref-struct-di "1.1.1" +"@hyperledger/anoncreds-nodejs@file:../anoncreds-rs/wrappers/javascript/packages/anoncreds-nodejs/build": + version "0.0.0" -"@hyperledger/anoncreds-shared@0.2.0-dev.4", "@hyperledger/anoncreds-shared@^0.2.0-dev.4": - version "0.2.0-dev.4" - resolved "https://registry.yarnpkg.com/@hyperledger/anoncreds-shared/-/anoncreds-shared-0.2.0-dev.4.tgz#8050647fcb153b594671270d4c51b3b423e575be" - integrity sha512-8hwXc9zab8MgXgwo0OL5bShxMAfDEiBAB1/r+8mbwgANefDZwHwNOkq0yQLwT2KfSsvH9la7N2ehrtUf5E2FKg== +"@hyperledger/anoncreds-shared@file:../anoncreds-rs/wrappers/javascript/packages/anoncreds-shared/build": + version "0.0.0" "@hyperledger/aries-askar-nodejs@^0.2.0-dev.1": version "0.2.0-dev.1" @@ -1711,7 +1700,7 @@ write-pkg "4.0.0" yargs "16.2.0" -"@mapbox/node-pre-gyp@1.0.11", "@mapbox/node-pre-gyp@^1.0.11": +"@mapbox/node-pre-gyp@1.0.11": version "1.0.11" resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz#417db42b7f5323d79e93b34a6d7a2a12c0df43fa" integrity sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ== @@ -10705,7 +10694,7 @@ reduce-flatten@^2.0.0: resolved "https://registry.yarnpkg.com/reduce-flatten/-/reduce-flatten-2.0.0.tgz#734fd84e65f375d7ca4465c69798c25c9d10ae27" integrity sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w== -ref-array-di@1.2.2, ref-array-di@^1.2.2: +ref-array-di@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/ref-array-di/-/ref-array-di-1.2.2.tgz#ceee9d667d9c424b5a91bb813457cc916fb1f64d" integrity sha512-jhCmhqWa7kvCVrWhR/d7RemkppqPUdxEil1CtTtm7FkZV8LcHHCK3Or9GinUiFP5WY3k0djUkMvhBhx49Jb2iA== @@ -10713,7 +10702,7 @@ ref-array-di@1.2.2, ref-array-di@^1.2.2: array-index "^1.0.0" debug "^3.1.0" -ref-struct-di@1.1.1, ref-struct-di@^1.1.0, ref-struct-di@^1.1.1: +ref-struct-di@^1.1.0, ref-struct-di@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ref-struct-di/-/ref-struct-di-1.1.1.tgz#5827b1d3b32372058f177547093db1fe1602dc10" integrity sha512-2Xyn/0Qgz89VT+++WP0sTosdm9oeowLP23wRJYhG4BFdMUrLj3jhwHZNEytYNYgtPKLNTP3KJX4HEgBvM1/Y2g==