@credo-ts/core
Version:
<p align="center"> <br /> <img alt="Credo Logo" src="https://github.com/openwallet-foundation/credo-ts/blob/c7886cb8377ceb8ee4efe8d264211e561a75072d/images/credo-logo.png" height="250px" /> </p> <h1 align="center"><b>Credo - Core</b></h1
361 lines (357 loc) • 23 kB
JavaScript
import { CredoError } from "../../error/CredoError.mjs";
import "../../error/index.mjs";
import { JsonTransformer } from "../../utils/JsonTransformer.mjs";
import "../../utils/index.mjs";
import { injectable } from "../../plugins/index.mjs";
import { __decorateMetadata } from "../../_virtual/_@oxc-project_runtime@0.112.0/helpers/decorateMetadata.mjs";
import { __decorate } from "../../_virtual/_@oxc-project_runtime@0.112.0/helpers/decorate.mjs";
import { getJwkHumanDescription } from "../kms/jwk/humanDescription.mjs";
import "../kms/index.mjs";
import { getPublicJwkFromVerificationMethod } from "../dids/domain/key-type/keyDidMapping.mjs";
import { uuid } from "../../utils/uuid.mjs";
import { DidsApi } from "../dids/DidsApi.mjs";
import "../dids/index.mjs";
import { ClaimFormat } from "../vc/models/ClaimFormat.mjs";
import { purposes } from "../vc/data-integrity/libraries/jsonld-signatures.mjs";
import { ANONCREDS_DATA_INTEGRITY_CRYPTOSUITE, AnonCredsDataIntegrityServiceSymbol } from "../vc/data-integrity/models/IAnonCredsDataIntegrityService.mjs";
import { W3cPresentation } from "../vc/models/presentation/W3cPresentation.mjs";
import { SignatureSuiteRegistry } from "../vc/data-integrity/SignatureSuiteRegistry.mjs";
import { W3cCredentialRepository } from "../vc/repository/W3cCredentialRepository.mjs";
import { W3cCredentialService } from "../vc/W3cCredentialService.mjs";
import "../vc/index.mjs";
import { MdocDeviceResponse } from "../mdoc/MdocDeviceResponse.mjs";
import { MdocApi } from "../mdoc/MdocApi.mjs";
import "../mdoc/index.mjs";
import { SdJwtVcApi } from "../sd-jwt-vc/SdJwtVcApi.mjs";
import "../sd-jwt-vc/index.mjs";
import { DifPresentationExchangeError } from "./DifPresentationExchangeError.mjs";
import { PresentationSubmissionLocation } from "./models/index.mjs";
import { getSphereonOriginalVerifiableCredential, getSphereonOriginalVerifiablePresentation, getVerifiablePresentationFromEncoded } from "./utils/transform.mjs";
import { getCredentialsForRequest } from "./utils/credentialSelection.mjs";
import { getPresentationsToCreate } from "./utils/presentationsToCreate.mjs";
import "./utils/index.mjs";
import { PEX, Status } from "@animo-id/pex";
import { PEVersion } from "@animo-id/pex/dist/main/lib/index.js";
//#region src/modules/dif-presentation-exchange/DifPresentationExchangeService.ts
var _ref;
let DifPresentationExchangeService = class DifPresentationExchangeService {
constructor(w3cCredentialService) {
this.w3cCredentialService = w3cCredentialService;
this.pex = new PEX();
}
async getCredentialsForRequest(agentContext, presentationDefinition) {
const credentialRecords = await this.queryCredentialForPresentationDefinition(agentContext, presentationDefinition);
return getCredentialsForRequest(agentContext, this.pex, presentationDefinition, credentialRecords);
}
/**
* Selects the credentials to use based on the output from `getCredentialsForRequest`
* Use this method if you don't want to manually select the credentials yourself.
*/
selectCredentialsForRequest(credentialsForRequest) {
if (!credentialsForRequest.areRequirementsSatisfied) throw new CredoError("Could not find the required credentials for the presentation submission");
const credentials = {};
for (const requirement of credentialsForRequest.requirements) for (const submission of requirement.submissionEntry.slice(0, requirement.needsCount)) {
if (!credentials[submission.inputDescriptorId]) credentials[submission.inputDescriptorId] = [];
credentials[submission.inputDescriptorId].push(submission.verifiableCredentials[0]);
}
return credentials;
}
validatePresentationDefinition(presentationDefinition) {
const validation = PEX.validateDefinition(presentationDefinition);
const errorMessages = this.formatValidated(validation);
if (errorMessages.length > 0) throw new DifPresentationExchangeError("Invalid presentation definition", { additionalMessages: errorMessages });
}
validatePresentationSubmission(presentationSubmission) {
const validation = PEX.validateSubmission(presentationSubmission);
const errorMessages = this.formatValidated(validation);
if (errorMessages.length > 0) throw new DifPresentationExchangeError("Invalid presentation submission", { additionalMessages: errorMessages });
}
validatePresentation(presentationDefinition, presentations, presentationSubmission) {
const result = this.pex.evaluatePresentation(presentationDefinition, Array.isArray(presentations) ? presentations.map(getSphereonOriginalVerifiablePresentation) : getSphereonOriginalVerifiablePresentation(presentations), {
limitDisclosureSignatureSuites: ["DataIntegrityProof.anoncreds-2023"],
presentationSubmission
});
if (result.areRequiredCredentialsPresent === Status.ERROR) throw new DifPresentationExchangeError("Invalid presentation", { additionalMessages: this.formatValidated(result.errors) });
}
formatValidated(v) {
return (Array.isArray(v) ? v : v ? [v] : []).filter((r) => r.status === Status.ERROR).map((r) => r.message).filter((r) => Boolean(r));
}
async createPresentation(agentContext, options) {
const { presentationDefinition, domain, challenge, mdocSessionTranscript } = options;
const presentationSubmissionLocation = options.presentationSubmissionLocation ?? PresentationSubmissionLocation.PRESENTATION;
const verifiablePresentationResultsWithFormat = [];
const presentationsToCreate = getPresentationsToCreate(options.credentialsForInputDescriptor);
for (const presentationToCreate of presentationsToCreate) {
const inputDescriptorIds = presentationToCreate.verifiableCredentials.map((c) => c.inputDescriptorId);
const inputDescriptorsForPresentation = presentationDefinition.input_descriptors.filter((inputDescriptor) => inputDescriptorIds.includes(inputDescriptor.id));
const presentationDefinitionForSubject = {
...presentationDefinition,
input_descriptors: inputDescriptorsForPresentation,
submission_requirements: void 0
};
if (presentationToCreate.claimFormat === ClaimFormat.MsoMdoc) {
if (presentationToCreate.verifiableCredentials.length !== 1) throw new DifPresentationExchangeError("Currently a Mdoc presentation can only be created from a single credential");
if (!mdocSessionTranscript) throw new DifPresentationExchangeError("Missing mdoc session transcript options for creating MDOC presentation.");
const { credential, inputDescriptorId } = presentationToCreate.verifiableCredentials[0];
const deviceResponse = await MdocDeviceResponse.createDeviceResponseWithPresentationDefinition(agentContext, {
mdocs: [credential.firstCredential],
presentationDefinition,
sessionTranscriptOptions: mdocSessionTranscript
});
if (presentationSubmissionLocation !== PresentationSubmissionLocation.EXTERNAL) throw new DifPresentationExchangeError("Only EXTERNAL DifPresentationExchangeSubmissionLocation supported for mdoc presentations");
verifiablePresentationResultsWithFormat.push({
verifiablePresentationResult: {
presentationSubmission: {
id: `MdocPresentationSubmission ${uuid()}`,
definition_id: presentationDefinition.id,
descriptor_map: [{
id: inputDescriptorId,
format: "mso_mdoc",
path: "$"
}]
},
verifiablePresentations: [deviceResponse.encoded],
presentationSubmissionLocation
},
claimFormat: presentationToCreate.claimFormat
});
} else {
const credentialsForPresentation = presentationToCreate.verifiableCredentials.map((c) => getSphereonOriginalVerifiableCredential(c.credential));
const extraProofOptions = this.shouldSignUsingAnonCredsDataIntegrity(presentationToCreate) ? {
typeSupportsSelectiveDisclosure: true,
type: `DataIntegrityProof.${ANONCREDS_DATA_INTEGRITY_CRYPTOSUITE}`
} : {};
const verifiablePresentationResult = await this.pex.verifiablePresentationFrom(presentationDefinitionForSubject, credentialsForPresentation, this.getPresentationSignCallback(agentContext, presentationToCreate), {
proofOptions: {
challenge,
domain,
...extraProofOptions
},
presentationSubmissionLocation
});
verifiablePresentationResultsWithFormat.push({
verifiablePresentationResult,
claimFormat: presentationToCreate.claimFormat
});
}
}
if (verifiablePresentationResultsWithFormat.length === 0) throw new DifPresentationExchangeError("No verifiable presentations created");
if (presentationsToCreate.length !== verifiablePresentationResultsWithFormat.length) throw new DifPresentationExchangeError("Invalid amount of verifiable presentations created");
const presentationSubmission = {
id: verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmission.id,
definition_id: verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmission.definition_id,
descriptor_map: []
};
verifiablePresentationResultsWithFormat.forEach(({ verifiablePresentationResult }, index) => {
const descriptorMap = verifiablePresentationResult.presentationSubmission.descriptor_map.map((d) => {
const descriptor = { ...d };
if (presentationSubmissionLocation === PresentationSubmissionLocation.EXTERNAL && verifiablePresentationResultsWithFormat.length > 1) descriptor.path = `$[${index}]`;
return descriptor;
});
presentationSubmission.descriptor_map.push(...descriptorMap);
});
return {
verifiablePresentations: verifiablePresentationResultsWithFormat.flatMap((resultWithFormat) => resultWithFormat.verifiablePresentationResult.verifiablePresentations.map((vp) => getVerifiablePresentationFromEncoded(agentContext, vp))),
encodedVerifiablePresentations: verifiablePresentationResultsWithFormat.flatMap((resultWithFormat) => resultWithFormat.verifiablePresentationResult.verifiablePresentations),
presentationSubmission,
presentationSubmissionLocation: verifiablePresentationResultsWithFormat[0].verifiablePresentationResult.presentationSubmissionLocation
};
}
getSigningAlgorithmFromVerificationMethod(verificationMethod, suitableAlgorithms) {
const publicJwk = getPublicJwkFromVerificationMethod(verificationMethod);
if (suitableAlgorithms) {
const possibleAlgorithms = publicJwk.supportedSignatureAlgorithms.filter((alg) => suitableAlgorithms?.includes(alg));
if (!possibleAlgorithms || possibleAlgorithms.length === 0) throw new DifPresentationExchangeError([
"Found no suitable signing algorithm.",
`Algorithms supported by Verification method: ${publicJwk.supportedSignatureAlgorithms.join(", ")}`,
`Suitable algorithms: ${suitableAlgorithms.join(", ")}`
].join("\n"));
return possibleAlgorithms[0];
}
return publicJwk.signatureAlgorithm;
}
getSigningAlgorithmsForPresentationDefinitionAndInputDescriptors(algorithmsSatisfyingDefinition, inputDescriptorAlgorithms) {
const allDescriptorAlgorithms = inputDescriptorAlgorithms.flat();
const algorithmsSatisfyingDescriptors = allDescriptorAlgorithms.filter((alg) => inputDescriptorAlgorithms.every((descriptorAlgorithmSet) => descriptorAlgorithmSet.includes(alg)));
const algorithmsSatisfyingPdAndDescriptorRestrictions = algorithmsSatisfyingDefinition.filter((alg) => algorithmsSatisfyingDescriptors.includes(alg));
if (algorithmsSatisfyingDefinition.length > 0 && algorithmsSatisfyingDescriptors.length > 0 && algorithmsSatisfyingPdAndDescriptorRestrictions.length === 0) throw new DifPresentationExchangeError("No signature algorithm found for satisfying restrictions of the presentation definition and input descriptors");
if (allDescriptorAlgorithms.length > 0 && algorithmsSatisfyingDescriptors.length === 0) throw new DifPresentationExchangeError("No signature algorithm found for satisfying restrictions of the input descriptors");
let suitableAlgorithms;
if (algorithmsSatisfyingPdAndDescriptorRestrictions.length > 0) suitableAlgorithms = algorithmsSatisfyingPdAndDescriptorRestrictions;
else if (algorithmsSatisfyingDescriptors.length > 0) suitableAlgorithms = algorithmsSatisfyingDescriptors;
else if (algorithmsSatisfyingDefinition.length > 0) suitableAlgorithms = algorithmsSatisfyingDefinition;
return suitableAlgorithms;
}
getSigningAlgorithmForJwtVc(presentationDefinition, verificationMethod) {
const algorithmsSatisfyingDefinition = presentationDefinition.format?.jwt_vc?.alg ?? [];
const inputDescriptorAlgorithms = presentationDefinition.input_descriptors.map((descriptor) => descriptor.format?.jwt_vc?.alg ?? []).filter((alg) => alg.length > 0);
const suitableAlgorithms = this.getSigningAlgorithmsForPresentationDefinitionAndInputDescriptors(algorithmsSatisfyingDefinition, inputDescriptorAlgorithms);
return this.getSigningAlgorithmFromVerificationMethod(verificationMethod, suitableAlgorithms);
}
getProofTypeForLdpVc(agentContext, presentationDefinition, verificationMethod) {
const algorithmsSatisfyingDefinition = presentationDefinition.format?.ldp_vc?.proof_type ?? [];
const inputDescriptorAlgorithms = presentationDefinition.input_descriptors.map((descriptor) => descriptor.format?.ldp_vc?.proof_type ?? []).filter((alg) => alg.length > 0);
const suitableSignatureSuites = this.getSigningAlgorithmsForPresentationDefinitionAndInputDescriptors(algorithmsSatisfyingDefinition, inputDescriptorAlgorithms);
const signatureSuiteRegistry = agentContext.dependencyManager.resolve(SignatureSuiteRegistry);
const publicJwk = getPublicJwkFromVerificationMethod(verificationMethod);
const supportedSignatureSuites = signatureSuiteRegistry.getAllByPublicJwkType(publicJwk);
if (supportedSignatureSuites.length === 0) throw new DifPresentationExchangeError(`Couldn't find a supported signature suite for the given jwk ${getJwkHumanDescription(publicJwk.toJson())}`);
if (suitableSignatureSuites) {
const foundSignatureSuite = supportedSignatureSuites.find((suite) => suitableSignatureSuites.includes(suite.proofType));
if (!foundSignatureSuite) throw new DifPresentationExchangeError([
"No possible signature suite found for the given verification method.",
`Verification method type: ${verificationMethod.type}`,
`jwk type: ${getJwkHumanDescription(publicJwk.toJson())}`,
`SupportedSignatureSuites: '${supportedSignatureSuites.map((s) => s.proofType).join(", ")}'`,
`SuitableSignatureSuites: ${suitableSignatureSuites.join(", ")}`
].join("\n"));
return foundSignatureSuite.proofType;
}
return supportedSignatureSuites[0].proofType;
}
/**
* if all submission descriptors have a format of di | ldp,
* and all credentials have an ANONCREDS_DATA_INTEGRITY proof we default to
* signing the presentation using the ANONCREDS_DATA_INTEGRITY_CRYPTOSUITE
*/
shouldSignUsingAnonCredsDataIntegrity(presentationToCreate, presentationSubmission) {
if (presentationToCreate.claimFormat !== ClaimFormat.LdpVp) return void 0;
const validDescriptorFormat = !presentationSubmission || presentationSubmission.descriptor_map.every((descriptor) => [
ClaimFormat.DiVc,
ClaimFormat.DiVp,
ClaimFormat.LdpVc,
ClaimFormat.LdpVp
].includes(descriptor.format));
const credentialAreSignedUsingAnonCredsDataIntegrity = presentationToCreate.verifiableCredentials.every(({ credential }) => {
const firstCredential = credential.firstCredential;
if (firstCredential.claimFormat !== ClaimFormat.LdpVc) return false;
return firstCredential.dataIntegrityCryptosuites.includes(ANONCREDS_DATA_INTEGRITY_CRYPTOSUITE);
});
return validDescriptorFormat && credentialAreSignedUsingAnonCredsDataIntegrity;
}
getPresentationSignCallback(agentContext, presentationToCreate) {
return async (callBackParams) => {
const { presentation: presentationInput, options, presentationDefinition, presentationSubmission } = callBackParams;
const { challenge, domain } = options.proofOptions ?? {};
if (!challenge) throw new CredoError("challenge MUST be provided when signing a Verifiable Presentation");
if (presentationToCreate.claimFormat === ClaimFormat.JwtVp) {
if (!presentationToCreate.subjectIds) throw new DifPresentationExchangeError("Cannot create presentation for credentials without subject id");
const verificationMethod = await this.getVerificationMethodForSubjectId(agentContext, presentationToCreate.subjectIds[0]);
const w3cPresentation = JsonTransformer.fromJSON(presentationInput, W3cPresentation);
w3cPresentation.holder = verificationMethod.controller;
return (await this.w3cCredentialService.signPresentation(agentContext, {
format: ClaimFormat.JwtVp,
alg: this.getSigningAlgorithmForJwtVc(presentationDefinition, verificationMethod),
verificationMethod: verificationMethod.id,
presentation: w3cPresentation,
challenge,
domain
})).encoded;
}
if (presentationToCreate.claimFormat === ClaimFormat.LdpVp) {
if (this.shouldSignUsingAnonCredsDataIntegrity(presentationToCreate, presentationSubmission)) {
presentationSubmission.descriptor_map = presentationSubmission.descriptor_map.map((descriptor) => ({
...descriptor,
format: "di_vp"
}));
return {
...(await agentContext.dependencyManager.resolve(AnonCredsDataIntegrityServiceSymbol).createPresentation(agentContext, {
presentationDefinition,
presentationSubmission,
selectedCredentialRecords: presentationToCreate.verifiableCredentials.map((vc) => vc.credential),
challenge
})).toJSON(),
presentation_submission: presentationSubmission
};
}
if (!presentationToCreate.subjectIds) throw new DifPresentationExchangeError("Cannot create presentation for credentials without subject id");
const verificationMethod = await this.getVerificationMethodForSubjectId(agentContext, presentationToCreate.subjectIds[0]);
const w3cPresentation = JsonTransformer.fromJSON(presentationInput, W3cPresentation);
w3cPresentation.holder = verificationMethod.controller;
return (await this.w3cCredentialService.signPresentation(agentContext, {
format: ClaimFormat.LdpVp,
proofType: this.getProofTypeForLdpVc(agentContext, presentationDefinition, verificationMethod),
proofPurpose: new purposes.AuthenticationProofPurpose({
challenge,
domain
}),
verificationMethod: verificationMethod.id,
presentation: w3cPresentation,
challenge,
domain
})).encoded;
}
if (presentationToCreate.claimFormat === ClaimFormat.SdJwtDc) {
const sdJwtInput = presentationInput;
if (!domain) throw new CredoError("Missing 'domain' property, unable to set required 'aud' property in SD-JWT KB-JWT");
const sdJwtVcApi = this.getSdJwtVcApi(agentContext);
const originalSdJwtVc = sdJwtVcApi.fromCompact(sdJwtInput.compactSdJwtVc);
originalSdJwtVc.kmsKeyId = presentationToCreate.verifiableCredentials[0].credential.credentialInstances[0].kmsKeyId;
return await sdJwtVcApi.present({
sdJwtVc: originalSdJwtVc,
presentationFrame: void 0,
verifierMetadata: {
audience: domain,
nonce: challenge,
issuedAt: Math.floor(Date.now() / 1e3)
},
additionalPayload: presentationToCreate.verifiableCredentials[0].additionalPayload
});
}
throw new DifPresentationExchangeError("Only JWT, SD-JWT-VC, JSONLD credentials are supported for a single presentation");
};
}
async getVerificationMethodForSubjectId(agentContext, subjectId) {
const didsApi = agentContext.dependencyManager.resolve(DidsApi);
if (!subjectId.startsWith("did:")) throw new DifPresentationExchangeError(`Only dids are supported as credentialSubject id. ${subjectId} is not a valid did`);
const didDocument = await didsApi.resolveDidDocument(subjectId);
if (!didDocument.authentication || didDocument.authentication.length === 0) throw new DifPresentationExchangeError(`No authentication verificationMethods found for did ${subjectId} in did document`);
let [verificationMethod] = didDocument.authentication;
if (typeof verificationMethod === "string") verificationMethod = didDocument.dereferenceKey(verificationMethod, ["authentication"]);
return verificationMethod;
}
/**
* Queries the wallet for credentials that match the given presentation definition. This only does an initial query based on the
* schema of the input descriptors. It does not do any further filtering based on the constraints in the input descriptors.
*/
async queryCredentialForPresentationDefinition(agentContext, presentationDefinition) {
const w3cCredentialRepository = agentContext.dependencyManager.resolve(W3cCredentialRepository);
const w3cQuery = [];
const sdJwtVcQuery = [];
const mdocQuery = [];
const presentationDefinitionVersion = PEX.definitionVersionDiscovery(presentationDefinition);
if (!presentationDefinitionVersion.version) throw new DifPresentationExchangeError("Unable to determine the Presentation Exchange version from the presentation definition", presentationDefinitionVersion.error ? { additionalMessages: [presentationDefinitionVersion.error] } : {});
if (presentationDefinitionVersion.version === PEVersion.v1) {
const pd = presentationDefinition;
for (const inputDescriptor of pd.input_descriptors) for (const schema of inputDescriptor.schema) {
sdJwtVcQuery.push({ vct: schema.uri });
w3cQuery.push({ $or: [
{ expandedTypes: [schema.uri] },
{ contexts: [schema.uri] },
{ types: [schema.uri] }
] });
mdocQuery.push({ docType: inputDescriptor.id });
}
} else if (presentationDefinitionVersion.version === PEVersion.v2) {} else throw new DifPresentationExchangeError(`Unsupported presentation definition version ${presentationDefinitionVersion.version}`);
const allRecords = [];
const w3cCredentialRecords = w3cQuery.length > 0 ? await w3cCredentialRepository.findByQuery(agentContext, { $or: w3cQuery }) : await w3cCredentialRepository.getAll(agentContext);
allRecords.push(...w3cCredentialRecords);
const sdJwtVcApi = this.getSdJwtVcApi(agentContext);
const sdJwtVcRecords = sdJwtVcQuery.length > 0 ? await sdJwtVcApi.findAllByQuery({ $or: sdJwtVcQuery }) : await sdJwtVcApi.getAll();
allRecords.push(...sdJwtVcRecords);
const mdocApi = this.getMdocApi(agentContext);
const mdocRecords = mdocQuery.length > 0 ? await mdocApi.findAllByQuery({ $or: mdocQuery }) : await mdocApi.getAll();
allRecords.push(...mdocRecords);
return allRecords;
}
getSdJwtVcApi(agentContext) {
return agentContext.dependencyManager.resolve(SdJwtVcApi);
}
getMdocApi(agentContext) {
return agentContext.dependencyManager.resolve(MdocApi);
}
};
DifPresentationExchangeService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof (_ref = typeof W3cCredentialService !== "undefined" && W3cCredentialService) === "function" ? _ref : Object])], DifPresentationExchangeService);
//#endregion
export { DifPresentationExchangeService };
//# sourceMappingURL=DifPresentationExchangeService.mjs.map