Receipts and verification
A Sentinel report should not ask to be trusted. Everything it claims can be recomputed from the report file and compared with what the chain stores.
The report file
The report is JSON in canonical form: object keys sorted, no whitespace, nothing optional left as undefined. Those exact bytes are what is hashed.
reportHash = keccak256(bytes), stored onchain.reportCidis the IPFS CIDv1 (raw codec, sha2-256) of the same bytes, stored onchain.ipfs add --cid-version 1 --raw-leavesgives the same CID.- The file is served at
/r/<cid>.json, and pinned to IPFS when the operator configures Pinata.
Signed inference receipts
Every model call produces a receipt:
InferenceReceipt {
provider, model, requestId,
promptHash, responseHash, // keccak256 of the exact prompt and answer
startedAt, finishedAt, // unix ms
promptTokens, completionTokens, costMicroUsd
}
Sentinel's attester key signs each receipt as EIP-712 typed data in the domain Sentinel, version 1, chain id 84532. The leaves of a Merkle tree are the EIP-712 struct hashes, in receipt order; pairs are hashed sorted (the OpenZeppelin MerkleProof convention) and an odd node is carried up unchanged. The root is receiptsRoot, stored onchain.
Each finding lists the indices of the receipts behind it: the analysis call that reported it and the quorum calls that voted on it.
These are honestly named: they prove that Sentinel's key vouched for these calls with these hashes and token counts. They do not prove what the model provider computed. When provider-signed receipts exist, they slot into the same field.
What Verify checks
The Verify in this browser button on a report page runs, with no Sentinel server involved after the file is loaded:
- The served bytes equal the canonical form of the parsed JSON.
- The CID of the bytes equals the CID the page was opened with.
- Every receipt's struct hash is right and its signature recovers to the attester named in the report.
- The Merkle root of the receipts equals
receiptsRoot. - Every model finding points at at least one receipt, and every index exists.
- Score, grade and counts follow from the findings.
AuditRegistry.getAudit(id)stores the same report hash, CID, receipts root, source hash, score, grade, counts and review flag.- The attester holds
ATTESTER_ROLEon the registry.
Onchain and offchain
| Onchain (Base Sepolia) | Offchain |
|---|---|
| Grade, score, tier, counts, review flag, timestamp, revoked flag | The report JSON and Markdown |
sourceHash, reportHash, receiptsRoot, reportCid | Your source code, unless it is verified source of a deployed contract |
| The NFT, its metadata and SVG | Prompts and model answers (only their hashes are in the receipts) |
Revocation
The revoker role can revoke an audit with a public reason, emitted in the Revoked event. The NFT's SVG gains a revoked stamp, the badge stops showing that audit, and the API reports it as revoked. Revoked audits are never deleted.