Sign every extracted field. Prove where it came from.
OCR gives you a value and a confidence score, but that confidence score is just the model grading its own work. AFiR-OCR ties each extracted field to the exact spot it came from, the model that read it, and that confidence score, all inside one tamper-evident receipt. Move the box around the text by even one pixel and the check fails. It works with any extraction engine, and you can check it offline with no secret key.
OCR output is just a claim. Then real money moves on it.
Invoices, contracts, claims, intake forms, IDs, titles: the value an extraction model reads off a page drives a decision and a payment. But there's no portable proof of what was read, where it came from, which model read it, or whether someone changed it after the fact.
The model grades its own homework
A confidence score is the model's own opinion of itself, not proof. It can't tell you whether the field was changed before it reached your system, and it won't catch tampering.
No link back to the page
Signing just the extracted text proves some text was produced. It says nothing about where on the page it came from or which model read it. The link to the source spot is gone.
Changes stay invisible
Saving bounding boxes in a database, with nothing that locks them together and flags tampering, means someone can quietly edit a value, a position, or a confidence score and no one will know. There's no audit trail.
Field, source region, model, confidence: all tied into one root, signed once.
The extraction engine reads the document. AFiR-OCR proves what it read. Each field becomes its own entry that ties together its value fingerprint, its page and bounding box, its block type, the model, and its confidence score. Those entries feed into a Merkle tree (a structure that locks a set of records together so any single change is detectable), and one ML-DSA-65 signature seals the whole document, no matter how many fields it has.
EXTRACTION ENGINE PER-FIELD BINDING SIGN + VERIFY ................. ................. ............. any OCR / IDP --> field + bbox --+ ( Mistral OCR, Textract, ... ) | v +-----------------------+ +----------------------+ | per-field leaf | ---> | Merkle root | | value_commit | | ( fields_root ) | | source_region(bbox) | +----------------------+ | block_class | | | model_ref | v | confidence | +----------------------+ +-----------------------+ | ML-DSA-65 signature | <-- one per doc | | PQ-anchored receipt | v +----------------------+ sensitive field? | value_commit = salted, v off-receipt ( redaction- offline, zero-secret verify compatible ) ( recompute root, check sig )
Four properties. Each one tested. Each one breaks the check if someone tampers with it.
All four tie into the same signature. Change a value, a position, a class, or a confidence score, and the recomputed root won't match, so the offline check fails.
Every field is tied to the exact spot it was read from.
The page number and bounding box feed into that field's entry. The tie between a field and its source spot is itself tamper-evident: move the box by one pixel and the entry changes, the root changes, and the check fails.
- source_region: { page, bbox: [x0, y0, x1, y1] }
- block_class tied in too: header | field | table_cell
- tamper test: moving the box 1px on one field fails the offline check
- each field is provably tied to the page coordinates it came from
Change the number after extraction and the receipt catches it.
A fingerprint of the value feeds into each field's entry. Change a value that's already tied in, say an invoice total, to a bigger number, and that entry changes, the fields_root changes, and the recomputed root no longer matches the signed root.
- value_commit tied per field into the Merkle entry
- tamper test: total changed after signing fails the check
- no shared secret needed to catch the change
- it catches every change, not just some of them
The tax ID never shows up in the receipt. The receipt still proves it was there.
For a sensitive field, the value's fingerprint is computed with a secret key kept off the receipt, so it can't be reversed. You can still prove the field existed, where it sat, what class it was, and its confidence score. The actual text never enters the chain.
- sensitive value_commit: uses a secret key, kept off the receipt
- tax_id / SSN / PAN / PHI provably missing from the receipt yet provably extracted
- whoever holds the secret key can re-prove the specific value on demand
- the wrong value won't match the fingerprint
The receipt says what was read. It never claims the value is right.
The asserts field is set to extraction_provenance_only. It proves the named model read the named region and produced that value at that confidence score. It does not claim the value is correct. We don't overclaim what this proves.
- asserts: "extraction_provenance_only"
- if you need correctness checked, that's a separate signed attestation
- that attestation points back to this receipt by its root
- the limit is stated plainly on the receipt itself
Built and tested for real. Real values. Tampering gets caught.
We ran this on a real two-page invoice with nine fields, straight from a fresh checkout. Six test checks, all six passed. The hashes below are the actual output of that test run.
Smoke test 6 of 6 pass
- 1✓Anyone can check the whole receipt offline, with no secret key
- 2✓Changing a tied value breaks the recomputed fields_root
- 3✓Moving a bounding box one pixel breaks the check
- 4✓A sensitive field stays out of the receipt yet is provably extracted
- 5✓Whoever holds the key can re-prove the redacted value; a wrong value fails
- 6✓One ML-DSA-65 signature per document, no matter how many fields
Signed test-run outputs
Nine fields: vendor, invoice no., date, bill-to tax ID (redacted), line item, amount, subtotal, tax, total. We tested the adapters against two different extraction-engine output formats. The engine reads the document; the receipt proves what it read.
Point it at your OCR. Get a receipt back. Check it offline.
Point AFiR-OCR at your extraction output, whether that's Mistral OCR, AWS Textract, or any engine that outputs regions and confidence scores. You get back one post-quantum-anchored receipt per document. Anyone with the public key can check the whole thing offline.
import { buildReceipt, verify } from "@hive/afir-ocr" // 1. run your OCR engine as usual. AFiR-OCR does not change it const ocr = await mistralOCR(invoicePdf) // fields + bbox + confidence // 2. tie every field to its source region, model, and confidence const receipt = await buildReceipt({ doc: invoicePdf, model_ref: "mir:mistral-ocr-4", fields: ocr.fields, // value, bbox, block_class, confidence sensitive: ["bill_to_tax_id"], // supports redaction asserts: "extraction_provenance_only", // proves origin, not correctness }) // receipt.fields_root -> 0x64c489...a8a34a1 // receipt.sig -> one ML-DSA-65 signature over the whole doc // 3. anyone checks it offline. No shared secret, no call home const { ok, why } = await verify(receipt, PUBLIC_KEY) // move one bbox by a pixel, change one total -> ok === false
Built for document pipelines that have to be auditable.
Anywhere a model reads a regulated document and the value drives a decision or a payment.
Invoices & AP
Extracted totals and line items feed payment runs. AFiR-OCR proves the total on the receipt is the same total read off the page, with no changes.
Contracts & claims
Clause and figure extraction with a provable tie back to the source region. It supports redaction for privileged or sensitive text.
Clinical intake forms
PHI fields stay out of the receipt while still being provably extracted. The data type lands on the receipt; the data itself does not.
OCR & IDP vendors
If you sell extraction to regulated buyers, AFiR-OCR is the proof their auditors will ask for. Your engine becomes the way it gets distributed.