import { canonicalJSONStringify, sha256, verifyReceipt } from './receipt'; import type { ChainValidationResult, ReceiptV2 } from './types'; export async function validateReceiptChain(receipts: ReceiptV2[]): Promise { if (receipts.length === 0) return { ok: true }; for (let i = 0; i < receipts.length; i++) { if (!verifyReceipt(receipts[i]!)) { return { ok: false, broken_at: i, reason: 'signature_invalid' }; } if (i === 0) continue; const prev = receipts[i - 1]!; const curr = receipts[i]!; const expectedHash = await computeChainHash(prev); if (curr.chain_validation_hash !== expectedHash) { return { ok: false, broken_at: i, reason: 'chain_hash_mismatch' }; } if (curr.parent_receipt_id !== prev.receipt_id) { return { ok: false, broken_at: i, reason: 'parent_reference_mismatch' }; } } return { ok: true }; } export async function computeChainHash(receipt: ReceiptV2): Promise { const canonical = canonicalJSONStringify({ receipt_id: receipt.receipt_id, workflow_id: receipt.workflow_id, cost_usd: receipt.cost_usd, signed_at: receipt.signed_at, signature: receipt.signature, }); return sha256(canonical); }