@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
229 lines (228 loc) • 12.4 kB
JavaScript
import { asArray } from "../../utils/array.mjs";
import { equalsIgnoreOrder } from "../../utils/deepEquality.mjs";
import "../../utils/index.mjs";
import { injectable } from "../../plugins/index.mjs";
import __decorateMetadata from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorateMetadata.mjs";
import __decorate from "../../_virtual/_@oxc-project_runtime@0.151.0/helpers/esm/decorate.mjs";
import { omitUndefinedFields } from "./proof-processing/normalization.mjs";
import { W3cDataIntegrityProcessingError, createInvalidResult, createIssue } from "./W3cDataIntegrityError.mjs";
import { W3cDataIntegrityCryptosuiteRegistry } from "./W3cDataIntegrityCryptosuiteRegistry.mjs";
import { assertCreatedProofPostconditions, validateProofFieldFormats, validateProofRequiredMembers } from "./proof-processing/proofValidation.mjs";
import { assertMultiProofDocument, assertSingleProofDocument, createW3cDataIntegrityProofOptions } from "./W3cDataIntegrityProof.mjs";
import { parseW3cDataIntegrityProofDocument } from "./proof-processing/parsing.mjs";
import { validateProofPurposeVerificationRelationship } from "./proof-processing/proofPurposeValidation.mjs";
import { validateProofChainStructure } from "./proof-processing/proofSetValidation.mjs";
//#region src/modules/w3c-di/W3cDataIntegrityProofService.ts
let W3cDataIntegrityProofService = class W3cDataIntegrityProofService {
constructor(dataIntegrityCryptosuiteRegistry) {
this.supportedMediaTypes = [
"application/json",
"application/ld+json",
"application/vc+ld+json"
];
this.dataIntegrityCryptosuiteRegistry = dataIntegrityCryptosuiteRegistry;
}
/**
* Implements VC Data Integrity v1.0 §4.2 "Add Proof" orchestration.
* Delegates cryptographic suite proof creation and enforces postconditions.
*/
async createProof(agentContext, options) {
const normalizedUnsecuredDocument = omitUndefinedFields(options.unsecuredDocument);
let cryptosuite;
try {
cryptosuite = this.dataIntegrityCryptosuiteRegistry.createByCryptosuite(agentContext, options.cryptosuite);
} catch (error) {
if (!(error instanceof Error)) throw error;
return {
created: false,
proof: null,
errors: [error instanceof W3cDataIntegrityProcessingError ? error.issue : createIssue("https://w3id.org/security#PROOF_GENERATION_ERROR", "Error creating Data Integrity proof", error.message)]
};
}
const proofOptions = createW3cDataIntegrityProofOptions(omitUndefinedFields({
cryptosuite: cryptosuite.cryptosuite,
verificationMethod: options.verificationMethod,
proofPurpose: options.proofPurpose,
created: options.created,
expires: options.expires,
challenge: options.challenge,
domain: options.domain,
nonce: options.nonce,
previousProof: options.previousProof
}));
try {
const proof = await cryptosuite.createProof(normalizedUnsecuredDocument, proofOptions);
assertCreatedProofPostconditions(proof, {
...proofOptions,
verificationMethod: options.verificationMethod
}, cryptosuite.cryptosuite);
return {
created: true,
proof
};
} catch (error) {
if (!(error instanceof Error)) throw error;
return {
created: false,
proof: null,
errors: [error instanceof W3cDataIntegrityProcessingError ? error.issue : createIssue("https://w3id.org/security#PROOF_GENERATION_ERROR", "Error creating Data Integrity proof", error.message)]
};
}
}
/**
* Implements VC Data Integrity v1.0 §4.4 "Verify Proof" algorithm.
* Steps 4-8 are enforced through required-member gate, policy checks,
* field-format validation, and cryptosuite verification.
*/
async verifyProof(agentContext, securedDocument, options = {}) {
try {
const normalizedSecuredDocument = omitUndefinedFields(securedDocument);
assertSingleProofDocument(normalizedSecuredDocument);
return await this.verifySingleProofCore(agentContext, normalizedSecuredDocument, options);
} catch (error) {
if (!(error instanceof Error)) throw error;
const issue = error instanceof W3cDataIntegrityProcessingError ? error.issue : createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", "Error verifying Data Integrity proof", error.message);
return createInvalidResult(issue);
}
}
/**
* Implements VC Data Integrity v1.0 §4.5 "Verify Proof Sets and Chains" algorithm.
* Performs chain-structure validation, then verifies each proof against
* reconstructed dependency-proof input.
*/
async verifyProofSetAndChain(agentContext, securedDocument, options = {}) {
try {
const normalizedSecuredDocument = omitUndefinedFields(securedDocument);
assertMultiProofDocument(normalizedSecuredDocument);
const proofs = normalizedSecuredDocument.proof;
const requiredMemberIssues = proofs.map((proof, index) => {
const requiredMemberValidationError = validateProofRequiredMembers(proof);
if (!requiredMemberValidationError) return void 0;
return createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", `Proof at index ${index} has invalid required members`, requiredMemberValidationError);
}).filter((issue) => issue !== void 0);
if (requiredMemberIssues.length > 0) return createInvalidResult(requiredMemberIssues[0], requiredMemberIssues.slice(1));
const proofChainIssues = validateProofChainStructure(proofs);
if (proofChainIssues.length > 0) return createInvalidResult(proofChainIssues[0], proofChainIssues.slice(1));
const proofPolicyIssues = proofs.map((proof, index) => {
const proofPolicyIssue = this.validateProofPolicy(proof, options);
if (!proofPolicyIssue) return void 0;
return createIssue(proofPolicyIssue.type, `Proof at index ${index} failed verification policy pre-check`, proofPolicyIssue.detail ?? proofPolicyIssue.title);
}).filter((issue) => issue !== void 0);
if (proofPolicyIssues.length > 0) return createInvalidResult(proofPolicyIssues[0], proofPolicyIssues.slice(1));
const proofFieldFormatIssues = proofs.map((proof, index) => {
const proofFieldFormatError = validateProofFieldFormats(proof);
if (!proofFieldFormatError) return void 0;
return createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", `Proof at index ${index} has invalid field formats`, proofFieldFormatError.errors[0]?.detail ?? proofFieldFormatError.errors[0]?.title);
}).filter((issue) => issue !== void 0);
if (proofFieldFormatIssues.length > 0) return createInvalidResult(proofFieldFormatIssues[0], proofFieldFormatIssues.slice(1));
const proofIdToIndex = this.createProofIdToIndexMap(proofs);
const { proof: _, ...unsecuredDocument } = normalizedSecuredDocument;
for (const [_index, proof] of proofs.entries()) {
const matchingProofs = this.getMatchingProofIndices(proof, proofIdToIndex).map((matchingProofIndex) => proofs[matchingProofIndex]);
const singleProofSecuredDocument = {
...unsecuredDocument,
proof
};
const chainInputDocument = this.reconstructProofInputDocument(unsecuredDocument, matchingProofs);
const verificationResult = await this.verifySingleProofCore(agentContext, singleProofSecuredDocument, options, chainInputDocument);
if (!verificationResult.verified) return verificationResult;
}
return {
verified: true,
verifiedDocument: unsecuredDocument,
mediaType: null
};
} catch (error) {
if (!(error instanceof Error)) throw error;
const issue = error instanceof W3cDataIntegrityProcessingError ? error.issue : createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", "Error verifying Data Integrity proof set/chain", error.message);
return createInvalidResult(issue);
}
}
/**
* Parses a secured document and dispatches to §4.4 or §4.5 verification paths.
*/
async verifyProofDocument(agentContext, options) {
const parseResult = parseW3cDataIntegrityProofDocument(options, this.supportedMediaTypes);
if (!parseResult.ok) return parseResult.result;
const { normalizedMediaType, securedDocument, verifyOptions } = parseResult.value;
const verificationResult = Array.isArray(securedDocument.proof) ? await this.verifyProofSetAndChain(agentContext, securedDocument, verifyOptions) : await this.verifyProof(agentContext, securedDocument, verifyOptions);
if (!verificationResult.verified) return verificationResult;
return {
...verificationResult,
mediaType: normalizedMediaType
};
}
/**
* Implements VC Data Integrity v1.0 §4.4 steps 5-8 for a single-proof input.
* Runs proof checks, field-format checks, relationship validation, and cryptosuite verification.
*/
async verifySingleProofCore(agentContext, securedDocument, options, unsecuredDocument = (() => {
const { proof: _, ...unsecuredDocument } = securedDocument;
return unsecuredDocument;
})()) {
const proof = securedDocument.proof;
const proofPolicyIssue = this.validateProofPolicy(proof, options);
if (proofPolicyIssue) return createInvalidResult(proofPolicyIssue);
const proofFieldFormatError = validateProofFieldFormats(proof);
if (proofFieldFormatError) return proofFieldFormatError;
const proofPurposeValidationError = await validateProofPurposeVerificationRelationship(agentContext, proof);
if (proofPurposeValidationError) return proofPurposeValidationError;
const cryptosuite = this.dataIntegrityCryptosuiteRegistry.createByCryptosuite(agentContext, proof.cryptosuite);
const proofVerificationInput = {
unsecuredDocument,
proof
};
const verificationResult = await cryptosuite.verifyProof(proofVerificationInput);
if (!verificationResult.verified || verificationResult.verifiedDocument === null) return createInvalidResult(createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", "Cryptosuite proof verification failed"));
return {
verified: true,
verifiedDocument: verificationResult.verifiedDocument,
mediaType: null
};
}
/**
* Implements VC Data Integrity v1.0 §4.4 steps 5-7 verification policy checks.
* Validates expected proofPurpose, domain, and challenge when provided.
*/
validateProofPolicy(proof, options) {
if (options.expectedProofPurpose && proof.proofPurpose !== options.expectedProofPurpose) return createIssue("https://w3id.org/security#PROOF_VERIFICATION_ERROR", "Proof purpose does not match expected proof purpose", `Expected '${options.expectedProofPurpose}', received '${proof.proofPurpose}'`);
if (options.domain) {
const expectedDomain = [...new Set(asArray(options.domain))];
const proofDomain = [...new Set(asArray(proof.domain))];
if (!equalsIgnoreOrder(expectedDomain, proofDomain)) return createIssue("https://w3id.org/security#INVALID_DOMAIN_ERROR", "Proof domain does not match expected domain");
}
if (options.challenge && proof.challenge !== options.challenge) return createIssue("https://w3id.org/security#INVALID_CHALLENGE_ERROR", "Proof challenge does not match expected challenge", `Expected '${options.challenge}', received '${proof.challenge ?? "undefined"}'`);
}
createProofIdToIndexMap(proofs) {
const proofIdToIndex = /* @__PURE__ */ new Map();
for (const [index, proof] of proofs.entries()) {
if (typeof proof.id !== "string") continue;
proofIdToIndex.set(proof.id, index);
}
return proofIdToIndex;
}
getMatchingProofIndices(proof, proofIdToIndex) {
const previousProofReferences = proof.previousProof ? Array.isArray(proof.previousProof) ? proof.previousProof : [proof.previousProof] : [];
const matchingProofIndices = [];
for (const previousProofReference of previousProofReferences) {
const matchingProofIndex = proofIdToIndex.get(previousProofReference);
if (matchingProofIndex !== void 0) matchingProofIndices.push(matchingProofIndex);
}
return matchingProofIndices;
}
/**
* Implements VC Data Integrity v1.0 §4.5 step 3.3 input reconstruction.
* Adds matching dependency proofs into the unsecured document for per-proof verification.
*/
reconstructProofInputDocument(unsecuredDocument, matchingProofs) {
if (matchingProofs.length === 0) return unsecuredDocument;
return {
...unsecuredDocument,
proof: matchingProofs.length === 1 ? matchingProofs[0] : matchingProofs
};
}
};
W3cDataIntegrityProofService = __decorate([injectable(), __decorateMetadata("design:paramtypes", [typeof W3cDataIntegrityCryptosuiteRegistry === "undefined" ? Object : W3cDataIntegrityCryptosuiteRegistry])], W3cDataIntegrityProofService);
//#endregion
export { W3cDataIntegrityProofService };
//# sourceMappingURL=W3cDataIntegrityProofService.mjs.map