import { DataIntegrityProof } from '@digitalbazaar/data-integrity'; import { cryptosuite as ecdsaRdfc2019 } from '@digitalbazaar/ecdsa-rdfc-2019-cryptosuite'; import { createVerifyCryptosuite as createEcdsaJcs2019VerifySuite } from '@digitalbazaar/ecdsa-jcs-2019-cryptosuite'; import { verifyCredential } from '@digitalbazaar/vc'; import { JsonCredential } from '../../shared/dto/jsonCredential.dto.js'; import { DidDocumentResolver } from '../../resolvers/didDocumentResolver.js'; import { CredentialValidationOptions } from '../../shared/dto/validationOptions.dto.js'; import { ValidationResult } from '../../shared/dto/validationResult.dto.js'; import { createDocumentLoader } from '../../shared/middleware/dataIntegrityDocumentLoader.js'; import { CredentialValidationTypes } from '../presentation/verifiablePresentationValidationReport.js'; /** * Returns a cryptosuite instance ready for verification given a cryptosuite name. * Some suites (ecdsa-jcs-2019) require a factory call; others export a static object. */ function getCryptosuite(name: string): unknown | null { switch (name) { case 'ecdsa-rdfc-2019': return ecdsaRdfc2019; case 'ecdsa-jcs-2019': return createEcdsaJcs2019VerifySuite(); default: return null; } } export class DataIntegrityProofValidator { constructor(private didDocumentResolver: DidDocumentResolver) {} /** * Verifies the DataIntegrityProof on a JSON-LD credential. * * Expiration, revocation, and issuance-date checks are intentionally * excluded here — those are run separately by the existing validators. */ async validate( credential: JsonCredential, opts?: CredentialValidationOptions, ): Promise { const cryptosuiteName = credential.proof?.cryptosuite; if (!cryptosuiteName) { return { valid: false, messages: [ `DataIntegrityProof is missing the required "cryptosuite" field. Credential: ${credential.id}`, ], }; } const cryptosuite = getCryptosuite(cryptosuiteName); if (!cryptosuite) { return { valid: false, messages: [ `Unsupported cryptosuite "${cryptosuiteName}". Credential: ${credential.id}`, ], }; } try { const suite = new DataIntegrityProof({ cryptosuite: cryptosuite as Parameters[0]['cryptosuite'], }); const documentLoader = createDocumentLoader(this.didDocumentResolver, opts); const result = await verifyCredential({ credential: { ...credential } as any, suite, documentLoader, // We perform status checking (revocation) separately via the existing // RevocationValidator, so pass a no-op here. checkStatus: async () => ({ verified: true }), }); if (!result.verified) { const messages = this.extractMessages(result, credential.id); return { valid: false, messages }; } return { valid: true }; } catch (error) { return { valid: false, messages: [ `DataIntegrityProof verification error. Credential: ${credential.id}. ${(error as Error).message}`, ], }; } } getValidationType(): CredentialValidationTypes { return CredentialValidationTypes.CredentialSignature; } private extractMessages(result: any, credentialId: string): string[] { const messages: string[] = []; if (result.error?.message) { messages.push( `DataIntegrityProof verification failed. Credential: ${credentialId}. ${result.error.message}`, ); } if (result.results?.length) { for (const r of result.results) { if (!r.verified && r.error?.message) { messages.push(r.error.message); } } } if (messages.length === 0) { messages.push( `DataIntegrityProof verification failed. Credential: ${credentialId}`, ); } return messages; } }