diff --git a/demo/package.json b/demo/package.json index 4ffbef505b..7988b23d87 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/artem/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/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/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 91befb8918..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, 'faber') + const faber = new Faber(9001, 'faber1') await faber.initializeAgent() return faber } @@ -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}` @@ -251,7 +252,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,6 +267,7 @@ export class Faber extends BaseAgent { requested_attributes: proofAttribute, }, }, + 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..f23cb8381b 100644 --- a/demo/src/FaberInquirer.ts +++ b/demo/src/FaberInquirer.ts @@ -91,13 +91,20 @@ 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) } 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/package.json b/packages/anoncreds-rs/package.json index aaafaf228d..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": "^0.2.0-dev.4", - "@hyperledger/anoncreds-shared": "^0.2.0-dev.4", + "@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 7249da2662..a937c66834 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsHolderService.ts @@ -8,6 +8,7 @@ import type { AnonCredsProofRequestRestriction, AnonCredsRequestedAttributeMatch, AnonCredsRequestedPredicateMatch, + AnonCredsW3CCredential, CreateCredentialRequestOptions, CreateCredentialRequestReturn, CreateLinkSecretOptions, @@ -18,35 +19,43 @@ 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' import type { - CredentialEntry, CredentialProve, CredentialRequestMetadata, JsonObject, + W3CCredentialEntry, } from '@hyperledger/anoncreds-shared' import { - AnonCredsCredentialRecord, - AnonCredsCredentialRepository, AnonCredsLinkSecretRepository, - AnonCredsRestrictionWrapper, - unqualifiedCredentialDefinitionIdRegex, AnonCredsRegistryService, + AnonCredsRestrictionWrapper, + AnonCredsW3CCredentialRecord, + AnonCredsW3CCredentialRepository, storeLinkSecret, + unqualifiedCredentialDefinitionIdRegex, } from '@aries-framework/anoncreds' -import { AriesFrameworkError, JsonTransformer, TypedArrayEncoder, injectable, utils } from '@aries-framework/core' +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, Credential, - CredentialRequest, CredentialRevocationState, LinkSecret, Presentation, RevocationRegistryDefinition, RevocationStatusList, - anoncreds, + W3CCredential, + W3CCredentialRequest, + W3CPresentation, } from '@hyperledger/anoncreds-shared' +import * as console from 'console' import { AnonCredsRsModuleConfig } from '../AnonCredsRsModuleConfig' import { AnonCredsRsError } from '../errors/AnonCredsRsError' @@ -77,7 +86,6 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { 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 @@ -176,11 +184,14 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { 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, 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 { @@ -194,7 +205,10 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { ): Promise { const { useLegacyProverDid, credentialDefinition, credentialOffer } = options let createReturnObj: - | { credentialRequest: CredentialRequest; credentialRequestMetadata: CredentialRequestMetadata } + | { + credentialRequest: W3CCredentialRequest | CredentialRequest + credentialRequestMetadata: CredentialRequestMetadata + } | undefined try { const linkSecretRepository = agentContext.dependencyManager.resolve(AnonCredsLinkSecretRepository) @@ -213,7 +227,11 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { ) } const { linkSecretId, linkSecretValue } = await this.createLinkSecret(agentContext, {}) - linkSecretRecord = await storeLinkSecret(agentContext, { linkSecretId, linkSecretValue, setAsDefault: true }) + linkSecretRecord = await storeLinkSecret(agentContext, { + linkSecretId, + linkSecretValue, + setAsDefault: true, + }) } if (!linkSecretRecord.value) { @@ -224,19 +242,34 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { if (!isLegacyIdentifier && useLegacyProverDid) { throw new AriesFrameworkError('Cannot use legacy prover_did with non-legacy identifiers') } - 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, - }) + + 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: createReturnObj.credentialRequest.toJson() as unknown as AnonCredsCredentialRequest, + credentialRequest: credRequest, credentialRequestMetadata: createReturnObj.credentialRequestMetadata.toJson() as unknown as AnonCredsCredentialRequestMetadata, } @@ -246,6 +279,134 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { } } + public async createW3CProof(agentContext: AgentContext, options: CreateProofOptions): Promise { + const { credentialDefinitions, proofRequest, selectedCredentials, schemas } = options + + let presentation: W3CPresentation | 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(AnonCredsW3CCredentialRepository) + + // Cache retrieved credentials in order to minimize storage calls + const retrievedCredentials = new Map() + + const credentialEntryFromAttribute = async ( + attribute: AnonCredsRequestedAttributeMatch | AnonCredsRequestedPredicateMatch + ): 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) + } + + // @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 + // 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: W3CCredentialEntry }[] = [] + + 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 = 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 storeCredential(agentContext: AgentContext, options: StoreCredentialOptions): Promise { const { credential, credentialDefinition, credentialRequestMetadata, revocationRegistry, schema } = options @@ -293,6 +454,112 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { }) ) + 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, + }) + ) + + console.log('\n------------Saved credential in W3C format------------') + console.log(w3cCredential) + console.log('------------------------------------------------------\n') + + return credentialId + } finally { + credentialObj?.handle.clear() + processedCredential?.handle.clear() + } + } + + public async storeW3CCredential(agentContext: AgentContext, options: StoreW3CCredentialOptions): Promise { + const { credential, credentialDefinition, credentialRequestMetadata, revocationRegistry, schema } = options + + const linkSecretRecord = await agentContext.dependencyManager + .resolve(AnonCredsLinkSecretRepository) + .getByLinkSecretId(agentContext, credentialRequestMetadata.link_secret_name) + + if (!linkSecretRecord.value) { + throw new AnonCredsRsError('Link Secret value not stored') + } + + const revocationRegistryDefinition = revocationRegistry?.definition as unknown as JsonObject + + const credentialId = options.credentialId ?? utils.uuid() + + let credentialObj: W3CCredential | undefined + let processedCredential: W3CCredential | undefined + try { + credentialObj = W3CCredential.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 credentialW3CRepository = agentContext.dependencyManager.resolve(AnonCredsW3CCredentialRepository) + + const methodName = agentContext.dependencyManager + .resolve(AnonCredsRegistryService) + .getRegistryForIdentifier(agentContext, credential.credentialSchema.definition).methodName + + await credentialW3CRepository.save( + agentContext, + new AnonCredsW3CCredentialRecord({ + credential: processedCredential.toJson() as unknown as AnonCredsW3CCredential, + credentialId, + linkSecretId: linkSecretRecord.linkSecretId, + issuerId: options.credentialDefinition.issuerId, + schemaName: schema.name, + schemaIssuerId: schema.issuerId, + schemaVersion: schema.version, + credentialRevocationId: processedCredential.revocationRegistryIndex?.toString(), + methodName, + }) + ) + + console.log('\n------------Saved credential in W3C format------------') + console.log(processedCredential.toJson()) + console.log('------------------------------------------------------\n') + + const credentialRepository = agentContext.dependencyManager.resolve(AnonCredsCredentialRepository) + + 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, + }) + ) + + console.log('\n------------Saved credential in Old format------------') + console.log(processedCredential.toLegacy().toJson()) + console.log('------------------------------------------------------\n') + return credentialId } finally { credentialObj?.handle.clear() @@ -304,22 +571,73 @@ export class AnonCredsRsHolderService implements AnonCredsHolderService { agentContext: AgentContext, options: GetCredentialOptions ): Promise { - const credentialRecord = await agentContext.dependencyManager - .resolve(AnonCredsCredentialRepository) - .getByCredentialId(agentContext, options.credentialId) + let credInfo: AnonCredsCredentialInfo + + 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 + 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: 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: 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, + credentialDefinitionId: credDef, + credentialId: credId, + schemaId: schema, + credentialRevocationId: revId, + revocationRegistryId: revRegId, + methodName: methodName, } } @@ -328,7 +646,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, @@ -339,21 +657,22 @@ 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 cred = credentialRecord.credential + return { + attributes: Object.fromEntries(Object.entries(cred.credentialSubject).map(([key, value]) => [key, value])), + credentialDefinitionId: cred.credentialSchema.definition, + credentialId: credentialRecord.credentialId, + schemaId: cred.credentialSchema.schema, + credentialRevocationId: credentialRecord.credentialRevocationId, + revocationRegistryId: cred.credentialStatus?.id, + methodName: credentialRecord.methodName, + } + }) } 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) } @@ -376,7 +695,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 } @@ -395,39 +714,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 } = {} - for (const attribute in credentialRecord.credential.values) { - attributes[attribute] = credentialRecord.credential.values[attribute].raw - } - return { - credentialInfo: { - attributes, - 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, - }, - interval: proofRequest.non_revoked, - } - }) + credentials = credentials.filter((value) => value.type == AnonCredsCredentialRecord.type) + + 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/AnonCredsRsIssuerService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsIssuerService.ts index 383c6e94e7..375be2969b 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 { Credential, CredentialDefinition, CredentialOffer, Schema } from '@hyperledger/anoncreds-shared' +import { + Credential, + W3CCredential, + CredentialDefinition, + W3CCredentialOffer, + Schema, +} from '@hyperledger/anoncreds-shared' import { AnonCredsRsError } from '../errors/AnonCredsRsError' @@ -89,7 +95,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 +122,7 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { schemaId = getUnqualifiedSchemaId(namespaceIdentifier, schemaName, schemaVersion) } - credentialOffer = CredentialOffer.create({ + credentialOffer = W3CCredentialOffer.create({ credentialDefinitionId, keyCorrectnessProof: keyCorrectnessProofRecord?.value, schemaId, @@ -131,6 +137,17 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { public async createCredential( agentContext: AgentContext, options: CreateCredentialOptions + ): Promise { + if (options.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 @@ -177,7 +194,70 @@ export class AnonCredsRsIssuerService implements AnonCredsIssuerService { credentialOffer: credentialOffer as unknown as JsonObject, credentialRequest: credentialRequest as unknown as JsonObject, revocationRegistryId, - attributeEncodedValues, + // 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 + + let credential: W3CCredential | 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 = W3CCredential.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, }) diff --git a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts index 26573309ff..497f541a48 100644 --- a/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts +++ b/packages/anoncreds-rs/src/services/AnonCredsRsVerifierService.ts @@ -3,11 +3,66 @@ 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 { 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 | undefined + try { + 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 @@ -35,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-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..853eac3b1c 100644 --- a/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts +++ b/packages/anoncreds/src/formats/AnonCredsCredentialFormatService.ts @@ -4,8 +4,15 @@ import type { AnonCredsCredentialOffer, AnonCredsCredentialRequest, 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, @@ -28,6 +35,7 @@ import type { CredentialPreviewAttributeOptions, LinkedAttachment, } from '@aries-framework/core' +import type { JsonObject } from '@hyperledger/anoncreds-shared' import { ProblemReportError, @@ -40,6 +48,8 @@ import { CredentialProblemReportReason, 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' @@ -218,6 +228,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService attachmentId, offerAttachment, credentialFormats, + isW3C, }: CredentialFormatAcceptOfferOptions ): Promise { const registryService = agentContext.dependencyManager.resolve(AnonCredsRegistryService) @@ -242,6 +253,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService credentialOffer, credentialDefinition, linkSecretId: credentialFormats?.anoncreds?.linkSecretId, + isW3C, }) credentialRecord.metadata.set( @@ -284,6 +296,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService attachmentId, offerAttachment, requestAttachment, + isW3C, }: CredentialFormatAcceptRequestOptions ): Promise { // Assert credential attributes @@ -307,6 +320,7 @@ export class AnonCredsCredentialFormatService implements CredentialFormatService credentialOffer, credentialRequest, credentialValues: convertAttributesToCredentialValues(credentialAttributes), + isW3C, }) if (credential.rev_reg_id) { @@ -336,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 @@ -357,62 +371,119 @@ 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 + let credentialId: string - 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.cred_def_id) - .getCredentialDefinition(agentContext, anonCredsCredential.cred_def_id) - if (!credentialDefinitionResult.credentialDefinition) { - throw new AriesFrameworkError( - `Unable to resolve credential definition ${anonCredsCredential.cred_def_id}: ${credentialDefinitionResult.resolutionMetadata.error} ${credentialDefinitionResult.resolutionMetadata.message}` - ) - } + if (isW3C) { + anonCredsCredential = attachment.getDataAsJson() - const schemaResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.cred_def_id) - .getSchema(agentContext, anonCredsCredential.schema_id) - if (!schemaResult.schema) { - throw new AriesFrameworkError( - `Unable to resolve schema ${anonCredsCredential.schema_id}: ${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.rev_reg_id) { - revocationRegistryResult = await registryService - .getRegistryForIdentifier(agentContext, anonCredsCredential.rev_reg_id) - .getRevocationRegistryDefinition(agentContext, anonCredsCredential.rev_reg_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}` + ) + } + + // 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}` + ) + } + } - if (!revocationRegistryResult.revocationRegistryDefinition) { + 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() + + 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.rev_reg_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) - - 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, - }) + 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) + + 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, + }) + } // If the credential is revocable, store the revocation identifiers in the credential record - if (anonCredsCredential.rev_reg_id) { + if (revocationRegistryResult?.revocationRegistryDefinitionId) { const credential = await anonCredsHolderService.getCredential(agentContext, { credentialId }) credentialRecord.metadata.add(AnonCredsCredentialMetadataKey, { 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 001aebb340..fa09791b75 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 { @@ -58,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' @@ -126,7 +128,7 @@ export class AnonCredsProofFormatService implements ProofFormatService + { attachmentId, proofFormats, isW3C }: FormatCreateRequestOptions ): Promise { const format = new ProofFormatSpec({ format: ANONCREDS_PRESENTATION_REQUEST, @@ -145,6 +147,7 @@ export class AnonCredsProofFormatService implements ProofFormatService() - const anoncredsFormat = proofFormats?.anoncreds const selectedCredentials = @@ -200,50 +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() + 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)) + ) - 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}'` - ) - } - } + 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 [, attributeGroup] of Object.entries(proofJson.requested_proof.revealed_attr_groups ?? {})) { - for (const [attributeName, attribute] of Object.entries(attributeGroup.values)) { + 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 '${attributeName}' is invalid. ` + + `The encoded value for '${referent}' 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)) - ) + 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)) + ) - const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) + const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) - return await verifierService.verifyProof(agentContext, { - proofRequest: proofRequestJson, - proof: proofJson, - schemas, - credentialDefinitions, - revocationRegistries, - }) + return await verifierService.verifyProof(agentContext, { + proofRequest: proofRequestJson, + proof: proofJson, + schemas, + credentialDefinitions, + revocationRegistries, + }) + } } public async getCredentialsForRequest( @@ -581,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 }) ) ) @@ -599,13 +621,23 @@ export class AnonCredsProofFormatService implements ProofFormatService i.cred_def_id)) ) - - const revocationRegistries = await getRevocationRegistriesForProof(agentContext, proofJson) - + 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: proofJson, + proof: presentation, schemas, credentialDefinitions, revocationRegistries, diff --git a/packages/anoncreds/src/models/exchange.ts b/packages/anoncreds/src/models/exchange.ts index 5213153ff9..584fbd27d6 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,45 @@ export interface AnonCredsCredential { signature_correctness_proof: unknown } +export interface AnonCredsW3CCredential { + context: Array + type: Array + issuer: string + issuanceDate?: string + credentialSchema: AnonCredsW3CCredentialSchema + credentialStatus?: AnonCredsW3CCredentialStatus + credentialSubject: Record + proof: SingleOrArray +} + +export interface AnonCredsW3CCredentialSchema { + type: string + definition: string + schema: string + encoding: string +} + +export interface AnonCredsW3CCredentialStatus { + type: string + id: string +} +export interface AnonCredsW3CCredentialProof { + type: string + signature: string +} + +export interface AnonCredsW3CPresentation { + context: Array + type: Array + verifiableCredential: Array + proof: AnonCredsW3CPresentationProof +} + +export interface AnonCredsW3CPresentationProof { + type: string + challenge: string + proofValue: string +} export interface AnonCredsProof { requested_proof: { revealed_attrs: Record< @@ -122,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/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/AnonCredsHolderService.ts b/packages/anoncreds/src/services/AnonCredsHolderService.ts index 47cefac8e3..aad65dc861 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderService.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderService.ts @@ -9,6 +9,7 @@ import type { CreateLinkSecretReturn, CreateLinkSecretOptions, GetCredentialsOptions, + StoreW3CCredentialOptions, } from './AnonCredsHolderServiceOptions' import type { AnonCredsCredentialInfo } from '../models' import type { AnonCredsProof } from '../models/exchange' @@ -20,11 +21,18 @@ export interface AnonCredsHolderService { createLinkSecret(agentContext: AgentContext, options: CreateLinkSecretOptions): Promise 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 a657279715..45f3e6e374 100644 --- a/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsHolderServiceOptions.ts @@ -1,10 +1,11 @@ import type { + AnonCredsCredential, AnonCredsCredentialInfo, AnonCredsCredentialRequestMetadata, AnonCredsSelectedCredentials, + AnonCredsW3CCredential, } from '../models' import type { - AnonCredsCredential, AnonCredsCredentialOffer, AnonCredsCredentialRequest, AnonCredsProofRequest, @@ -56,8 +57,22 @@ export interface StoreCredentialOptions { } } +export interface StoreW3CCredentialOptions { + credentialRequestMetadata: AnonCredsCredentialRequestMetadata + credential: AnonCredsW3CCredential + credentialDefinition: AnonCredsCredentialDefinition + schema: AnonCredsSchema + credentialDefinitionId: string + credentialId?: string + revocationRegistry?: { + id: string + definition: AnonCredsRevocationRegistryDefinition + } +} + export interface GetCredentialOptions { credentialId: string + isLegacy?: boolean } export interface GetCredentialsOptions { @@ -82,6 +97,7 @@ export interface GetCredentialsForProofRequestOptions { start?: number limit?: number extraQuery?: ReferentWalletQuery + isLegacy?: boolean } export type GetCredentialsForProofRequestReturn = Array<{ @@ -94,6 +110,7 @@ export interface CreateCredentialRequestOptions { credentialDefinition: AnonCredsCredentialDefinition linkSecretId?: string useLegacyProverDid?: boolean + isW3C?: boolean } export interface CreateCredentialRequestReturn { 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/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts index 1bdd959f15..adea238c5a 100644 --- a/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts +++ b/packages/anoncreds/src/services/AnonCredsVerifierServiceOptions.ts @@ -6,9 +6,11 @@ import type { AnonCredsSchema, } from '../models/registry' +import { AnonCredsW3CPresentation } from '../models/exchange' + export interface VerifyProofOptions { proofRequest: AnonCredsProofRequest - proof: AnonCredsProof + proof: AnonCredsW3CPresentation | AnonCredsProof schemas: { [schemaId: string]: AnonCredsSchema } diff --git a/packages/anoncreds/src/utils/getRevocationRegistries.ts b/packages/anoncreds/src/utils/getRevocationRegistries.ts index 0141bf257b..dd9db2d8e5 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,6 +157,60 @@ export async function getRevocationRegistriesForRequest( } } +export async function getRevocationRegistriesForProofW3C(agentContext: AgentContext, proof: AnonCredsW3CPresentation) { + const revocationRegistries: VerifyProofOptions['revocationRegistries'] = {} + + const revocationRegistryPromises = [] + 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 + + 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 +} + export async function getRevocationRegistriesForProof(agentContext: AgentContext, proof: AnonCredsProof) { const revocationRegistries: VerifyProofOptions['revocationRegistries'] = {} diff --git a/packages/core/src/modules/credentials/CredentialsApi.ts b/packages/core/src/modules/credentials/CredentialsApi.ts index 4a0a6ac349..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) diff --git a/packages/core/src/modules/credentials/CredentialsApiOptions.ts b/packages/core/src/modules/credentials/CredentialsApiOptions.ts index 19e9f17295..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 } /** diff --git a/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts b/packages/core/src/modules/credentials/formats/CredentialFormatServiceOptions.ts index 9e438c6d1c..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 { @@ -87,6 +88,7 @@ export interface CredentialFormatAcceptOfferOptions attachmentId?: string offerAttachment: Attachment + isW3C?: boolean } export interface CredentialFormatCreateOfferReturn extends CredentialFormatCreateReturn { @@ -105,6 +107,7 @@ export interface CredentialFormatAcceptRequestOptions, 'createOffer'> autoAcceptCredential?: AutoAcceptCredential comment?: string + isW3C?: boolean } export interface AcceptCredentialOfferOptions { diff --git a/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts b/packages/core/src/modules/credentials/protocol/v2/CredentialFormatCoordinator.ts index c8fe64e86d..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 }) @@ -331,6 +334,7 @@ export class CredentialFormatCoordinator offerAttachment, credentialRecord, credentialFormats, + isW3C: offerMessage.isW3C, }) requestAttachments.push(attachment) @@ -486,6 +490,7 @@ export class CredentialFormatCoordinator offerAttachment, credentialRecord, credentialFormats, + isW3C: offerMessage?.isW3C, }) credentialAttachments.push(attachment) @@ -517,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) @@ -538,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 5d764fb5aa..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 attachment.id === id) } 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 { + throw new AriesFrameworkError('Not supported') + } + 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 +204,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 ) @@ -202,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==