/** * DSB Verifier SDK * JavaScript/TypeScript SDK for verifying eResidency and eCitizenship credentials */ import { eResidentCredentialSchema, eCitizenCredentialSchema, type VerifiablePresentation } from '@the-order/schemas'; import { DIDResolver } from '@the-order/auth'; export interface VerifierConfig { issuerDid: string; schemaRegistryUrl?: string; statusListUrl?: string; } export interface VerificationResult { valid: boolean; credentialId: string; credentialType: string[]; verifiedAt: string; errors?: string[]; warnings?: string[]; status?: 'active' | 'suspended' | 'revoked' | 'expired'; } /** * DSB Verifier */ export class DSBVerifier { private resolver: DIDResolver; private config: VerifierConfig; constructor(config: VerifierConfig) { this.config = config; this.resolver = new DIDResolver(); } /** * Verify eResident credential */ async verifyEResidentCredential(credential: unknown): Promise { const errors: string[] = []; const warnings: string[] = []; try { // Validate schema const validated = eResidentCredentialSchema.parse(credential); const credentialId = (validated as any).id || 'unknown'; // Verify issuer if (validated.issuer !== this.config.issuerDid) { errors.push(`Invalid issuer: expected ${this.config.issuerDid}, got ${validated.issuer}`); } // Verify proof if (validated.proof) { const proofValid = await this.verifyProof(validated.issuer, validated, validated.proof); if (!proofValid) { errors.push('Proof verification failed'); } } else { errors.push('Credential missing proof'); } // Check expiration if (validated.expirationDate) { const expirationDate = new Date(validated.expirationDate); if (expirationDate < new Date()) { errors.push('Credential has expired'); } } // Check credential status if (validated.credentialStatus) { const status = await this.checkCredentialStatus(validated.credentialStatus.id); if (status !== 'active') { errors.push(`Credential status: ${status}`); } } // Verify evidence if (validated.evidence && validated.evidence.length > 0) { for (const evidence of validated.evidence) { if (evidence.result === 'fail') { warnings.push(`Evidence check failed: ${evidence.type}`); } } } return { valid: errors.length === 0, credentialId, credentialType: validated.type, verifiedAt: new Date().toISOString(), errors: errors.length > 0 ? errors : undefined, warnings: warnings.length > 0 ? warnings : undefined, status: errors.length === 0 ? 'active' : undefined, }; } catch (error) { return { valid: false, credentialId: 'unknown', credentialType: [], verifiedAt: new Date().toISOString(), errors: [error instanceof Error ? error.message : 'Unknown error'], }; } } /** * Verify eCitizen credential */ async verifyECitizenCredential(credential: unknown): Promise { const errors: string[] = []; const warnings: string[] = []; try { // Validate schema const validated = eCitizenCredentialSchema.parse(credential); const credentialId = (validated as any).id || 'unknown'; // Verify issuer if (validated.issuer !== this.config.issuerDid) { errors.push(`Invalid issuer: expected ${this.config.issuerDid}, got ${validated.issuer}`); } // Verify proof if (validated.proof) { const proofValid = await this.verifyProof(validated.issuer, validated, validated.proof); if (!proofValid) { errors.push('Proof verification failed'); } } else { errors.push('Credential missing proof'); } // Check expiration if (validated.expirationDate) { const expirationDate = new Date(validated.expirationDate); if (expirationDate < new Date()) { errors.push('Credential has expired'); } } // Check credential status if (validated.credentialStatus) { const status = await this.checkCredentialStatus(validated.credentialStatus.id); if (status !== 'active') { errors.push(`Credential status: ${status}`); } } // Verify evidence if (validated.evidence && validated.evidence.length > 0) { for (const evidence of validated.evidence) { if (evidence.result === 'fail') { warnings.push(`Evidence check failed: ${evidence.type}`); } } } // Verify oath date if (validated.credentialSubject.oathDate) { const oathDate = new Date(validated.credentialSubject.oathDate); if (oathDate > new Date()) { warnings.push('Oath date is in the future'); } } return { valid: errors.length === 0, credentialId, credentialType: validated.type, verifiedAt: new Date().toISOString(), errors: errors.length > 0 ? errors : undefined, warnings: warnings.length > 0 ? warnings : undefined, status: errors.length === 0 ? 'active' : undefined, }; } catch (error) { return { valid: false, credentialId: 'unknown', credentialType: [], verifiedAt: new Date().toISOString(), errors: [error instanceof Error ? error.message : 'Unknown error'], }; } } /** * Verify verifiable presentation */ async verifyPresentation(presentation: VerifiablePresentation, challenge?: string): Promise { const errors: string[] = []; const warnings: string[] = []; try { // Verify challenge if provided if (challenge && presentation.proof.challenge !== challenge) { errors.push('Challenge mismatch'); } // Verify holder if (!presentation.holder) { errors.push('Presentation missing holder'); } // Verify proof if (presentation.proof) { const proofValid = await this.verifyProof(presentation.holder, presentation, presentation.proof); if (!proofValid) { errors.push('Presentation proof verification failed'); } } else { errors.push('Presentation missing proof'); } // Verify all credentials in presentation const credentialResults = await Promise.all( presentation.verifiableCredential.map((cred) => { if (cred.type.includes('eResidentCredential')) { return this.verifyEResidentCredential(cred); } else if (cred.type.includes('eCitizenCredential')) { return this.verifyECitizenCredential(cred); } else { return Promise.resolve({ valid: false, credentialId: 'unknown', credentialType: [], verifiedAt: new Date().toISOString(), errors: ['Unknown credential type'], }); } }) ); const allValid = credentialResults.every((result) => result.valid); if (!allValid) { errors.push('One or more credentials failed verification'); credentialResults.forEach((result) => { if (result.errors) { errors.push(...result.errors); } }); } return { valid: errors.length === 0 && allValid, credentialId: presentation.verifiableCredential[0]?.id || 'unknown', credentialType: presentation.verifiableCredential.flatMap((cred) => cred.type), verifiedAt: new Date().toISOString(), errors: errors.length > 0 ? errors : undefined, warnings: warnings.length > 0 ? warnings : undefined, status: errors.length === 0 && allValid ? 'active' : undefined, }; } catch (error) { return { valid: false, credentialId: 'unknown', credentialType: [], verifiedAt: new Date().toISOString(), errors: [error instanceof Error ? error.message : 'Unknown error'], }; } } /** * Verify proof */ private async verifyProof( did: string, document: unknown, proof: { type: string; created: string; proofPurpose: string; verificationMethod: string; jws?: string; } ): Promise { try { if (!proof.jws) { return false; } // Create message to verify (credential without proof) const documentWithoutProof = { ...document } as any; delete documentWithoutProof.proof; const message = JSON.stringify(documentWithoutProof); // Verify signature using DID resolver const isValid = await this.resolver.verifySignature(did, message, proof.jws); return isValid; } catch (error) { console.error('Proof verification error:', error); return false; } } /** * Check credential status */ private async checkCredentialStatus(statusListId: string): Promise<'active' | 'suspended' | 'revoked' | 'expired'> { // TODO: Implement status list checking // const response = await fetch(`${this.config.statusListUrl}/${statusListId}`); // const statusList = await response.json(); // return statusList.status; return 'active'; // Placeholder } } /** * Create verifier instance */ export function createVerifier(config: VerifierConfig): DSBVerifier { return new DSBVerifier(config); }