@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
195 lines (191 loc) • 10.7 kB
JavaScript
import { CredoError } from "../../../error/CredoError.mjs";
import "../../../error/index.mjs";
import { Hasher } from "../../../crypto/hashes/Hasher.mjs";
import { ClaimFormat } from "../../vc/models/ClaimFormat.mjs";
import "../../../crypto/index.mjs";
import { W3cCredentialRecord } from "../../vc/repository/W3cCredentialRecord.mjs";
import "../../vc/index.mjs";
import { MdocRecord } from "../../mdoc/repository/MdocRecord.mjs";
import { SdJwtVcRecord } from "../../sd-jwt-vc/repository/SdJwtVcRecord.mjs";
import { MdocDeviceResponse } from "../../mdoc/MdocDeviceResponse.mjs";
import "../../mdoc/index.mjs";
import "../../sd-jwt-vc/index.mjs";
import { DifPresentationExchangeError } from "../DifPresentationExchangeError.mjs";
import { getSphereonOriginalVerifiableCredential } from "./transform.mjs";
import { decodeSdJwtSync, getClaimsSync } from "@sd-jwt/decode";
import { Status } from "@animo-id/pex";
import { SubmissionRequirementMatchType } from "@animo-id/pex/dist/main/lib/evaluation/core/index.js";
import { JSONPath } from "@astronautlabs/jsonpath";
import { Rules } from "@sphereon/pex-models";
//#region src/modules/dif-presentation-exchange/utils/credentialSelection.ts
async function getCredentialsForRequest(agentContext, pex, presentationDefinition, credentialRecords) {
const encodedCredentials = credentialRecords.map(getSphereonOriginalVerifiableCredential);
const selectResultsRaw = pex.selectFrom(presentationDefinition, encodedCredentials);
const selectResults = {
...selectResultsRaw,
matches: selectResultsRaw.matches ?? [],
verifiableCredential: selectResultsRaw.verifiableCredential?.map((selectedEncoded, index) => {
const credentialRecordIndex = selectResultsRaw.vcIndexes?.[index];
if (credentialRecordIndex === void 0 || credentialRecordIndex === -1) throw new DifPresentationExchangeError("Unable to find credential in credential records.");
const credentialRecord = credentialRecords[credentialRecordIndex];
if (credentialRecord instanceof SdJwtVcRecord) {
const { jwt, disclosures } = decodeSdJwtSync(selectedEncoded, Hasher.hash);
const prettyClaims = getClaimsSync(jwt.payload, disclosures, Hasher.hash);
return {
claimFormat: ClaimFormat.SdJwtDc,
credentialRecord,
disclosedPayload: prettyClaims
};
}
if (credentialRecord instanceof MdocRecord) return {
claimFormat: ClaimFormat.MsoMdoc,
credentialRecord,
disclosedPayload: {}
};
if (credentialRecord instanceof W3cCredentialRecord) return {
claimFormat: credentialRecord.firstCredential.claimFormat,
credentialRecord
};
throw new CredoError("Unrecognized credential record type");
}) ?? []
};
const presentationSubmission = {
requirements: [],
areRequirementsSatisfied: false,
name: presentationDefinition.name,
purpose: presentationDefinition.purpose
};
if (!presentationDefinition.submission_requirements || presentationDefinition.submission_requirements.length === 0) presentationSubmission.requirements = getSubmissionRequirementsForAllInputDescriptors(presentationDefinition.input_descriptors, selectResults);
else presentationSubmission.requirements = getSubmissionRequirements(presentationDefinition, selectResults);
const allEntries = presentationSubmission.requirements.flatMap((requirement) => requirement.submissionEntry);
const inputDescriptorsForMdocCredential = /* @__PURE__ */ new Map();
for (const entry of allEntries) for (const verifiableCredential of entry.verifiableCredentials) {
if (verifiableCredential.claimFormat !== ClaimFormat.MsoMdoc) continue;
const set = inputDescriptorsForMdocCredential.get(verifiableCredential) ?? /* @__PURE__ */ new Set();
set.add(entry.inputDescriptorId);
inputDescriptorsForMdocCredential.set(verifiableCredential, set);
}
for (const [verifiableCredential, inputDescriptorIds] of inputDescriptorsForMdocCredential.entries()) {
if (verifiableCredential.claimFormat !== ClaimFormat.MsoMdoc) continue;
const inputDescriptorsForCredential = presentationDefinition.input_descriptors.filter(({ id }) => inputDescriptorIds.has(id));
const mdoc = verifiableCredential.credentialRecord.firstCredential;
verifiableCredential.disclosedPayload = await MdocDeviceResponse.limitDisclosureToInputDescriptor(agentContext, {
inputDescriptor: {
id: mdoc.docType,
format: { mso_mdoc: { alg: [] } },
constraints: {
limit_disclosure: "required",
fields: inputDescriptorsForCredential.flatMap((i) => i.constraints?.fields ?? [])
}
},
mdoc
});
}
if (presentationSubmission.requirements.length === 0) throw new DifPresentationExchangeError("Presentation Definition does not require any credentials. Optional credentials are not included in the presentation submission.");
if (selectResults.areRequiredCredentialsPresent === Status.ERROR) return presentationSubmission;
return {
...presentationSubmission,
areRequirementsSatisfied: presentationSubmission.requirements.every((requirement) => requirement.isRequirementSatisfied)
};
}
function getSubmissionRequirements(presentationDefinition, selectResults) {
const submissionRequirements = [];
const matches = selectResults.matches;
if (!matches.every((match) => match.type === SubmissionRequirementMatchType.SubmissionRequirement && match.from)) throw new DifPresentationExchangeError(`Expected all matches to be of type '${SubmissionRequirementMatchType.SubmissionRequirement}' with 'from' key.`);
presentationDefinition.submission_requirements?.forEach((submissionRequirement, submissionRequirementIndex) => {
if (submissionRequirement.from_nested) throw new DifPresentationExchangeError("Presentation definition contains requirement using 'from_nested', which is not supported yet.");
if (!submissionRequirement.from) throw new DifPresentationExchangeError("Missing 'from' in submission requirement match");
const match = matches.find((match) => match.id === submissionRequirementIndex);
if (!match) throw new Error(`Unable to find a match for submission requirement with index '${submissionRequirementIndex}'`);
if (submissionRequirement.rule === Rules.All) {
const selectedSubmission = getSubmissionRequirementRuleAll(submissionRequirement, presentationDefinition, selectResults.verifiableCredential, match);
submissionRequirements.push(selectedSubmission);
} else {
const selectedSubmission = getSubmissionRequirementRulePick(submissionRequirement, presentationDefinition, selectResults.verifiableCredential, match);
submissionRequirements.push(selectedSubmission);
}
});
return submissionRequirements.filter((requirement) => requirement.needsCount > 0);
}
function getSubmissionRequirementsForAllInputDescriptors(inputDescriptors, selectResults) {
const submissionRequirements = [];
const matches = selectResults.matches;
if (!matches.every((match) => match.type === SubmissionRequirementMatchType.InputDescriptor)) throw new DifPresentationExchangeError(`Expected all matches to be of type '${SubmissionRequirementMatchType.InputDescriptor}' when.`);
for (const inputDescriptor of inputDescriptors) {
const submission = getSubmissionForInputDescriptor(inputDescriptor, selectResults.verifiableCredential, matches);
submissionRequirements.push({
rule: Rules.Pick,
needsCount: 1,
submissionEntry: [submission],
isRequirementSatisfied: submission.verifiableCredentials.length >= 1
});
}
return submissionRequirements;
}
function getSubmissionRequirementRuleAll(submissionRequirement, presentationDefinition, verifiableCredentials, match) {
if (!submissionRequirement.from) throw new DifPresentationExchangeError("Missing 'from' in submission requirement match.");
const selectedSubmission = {
rule: Rules.All,
needsCount: 0,
name: submissionRequirement.name,
purpose: submissionRequirement.purpose,
submissionEntry: [],
isRequirementSatisfied: false
};
for (const inputDescriptor of presentationDefinition.input_descriptors) {
if (!inputDescriptor.group?.includes(match.from)) continue;
const submission = getSubmissionForInputDescriptor(inputDescriptor, verifiableCredentials, match.input_descriptors);
selectedSubmission.needsCount += 1;
selectedSubmission.submissionEntry.push(submission);
}
return {
...selectedSubmission,
isRequirementSatisfied: selectedSubmission.submissionEntry.every((submission) => submission.verifiableCredentials.length >= 1)
};
}
function getSubmissionRequirementRulePick(submissionRequirement, presentationDefinition, verifiableCredentials, match) {
if (!submissionRequirement.from) throw new DifPresentationExchangeError("Missing 'from' in submission requirement match.");
const selectedSubmission = {
rule: Rules.Pick,
needsCount: submissionRequirement.count ?? submissionRequirement.min ?? 1,
name: submissionRequirement.name,
purpose: submissionRequirement.purpose,
submissionEntry: [],
isRequirementSatisfied: false
};
const satisfiedSubmissions = [];
const unsatisfiedSubmissions = [];
for (const inputDescriptor of presentationDefinition.input_descriptors) {
if (!inputDescriptor.group?.includes(match.from)) continue;
const submission = getSubmissionForInputDescriptor(inputDescriptor, verifiableCredentials, match.input_descriptors);
if (submission.verifiableCredentials.length >= 1) satisfiedSubmissions.push(submission);
else unsatisfiedSubmissions.push(submission);
}
return {
...selectedSubmission,
isRequirementSatisfied: satisfiedSubmissions.length >= selectedSubmission.needsCount,
submissionEntry: satisfiedSubmissions.length >= selectedSubmission.needsCount ? satisfiedSubmissions : [...satisfiedSubmissions, ...unsatisfiedSubmissions]
};
}
function getSubmissionForInputDescriptor(inputDescriptor, verifiableCredentials, matches) {
const matchesForInputDescriptor = matches.filter((m) => m.id === inputDescriptor.id);
const submissionEntry = {
inputDescriptorId: inputDescriptor.id,
name: inputDescriptor.name,
purpose: inputDescriptor.purpose,
verifiableCredentials: matchesForInputDescriptor.flatMap((matchForInputDescriptor) => extractCredentialsFromInputDescriptorMatch(matchForInputDescriptor, verifiableCredentials))
};
if (!matchesForInputDescriptor?.length) return submissionEntry;
return submissionEntry;
}
function extractCredentialsFromInputDescriptorMatch(match, availableCredentials) {
const verifiableCredentials = [];
for (const vcPath of match.vc_path) {
const [verifiableCredential] = JSONPath.query({ verifiableCredential: availableCredentials }, vcPath);
verifiableCredentials.push(verifiableCredential);
}
return verifiableCredentials;
}
//#endregion
export { getCredentialsForRequest };
//# sourceMappingURL=credentialSelection.mjs.map