SENTINEL

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.
  • reportCid is the IPFS CIDv1 (raw codec, sha2-256) of the same bytes, stored onchain. ipfs add --cid-version 1 --raw-leaves gives 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:

  1. The served bytes equal the canonical form of the parsed JSON.
  2. The CID of the bytes equals the CID the page was opened with.
  3. Every receipt's struct hash is right and its signature recovers to the attester named in the report.
  4. The Merkle root of the receipts equals receiptsRoot.
  5. Every model finding points at at least one receipt, and every index exists.
  6. Score, grade and counts follow from the findings.
  7. AuditRegistry.getAudit(id) stores the same report hash, CID, receipts root, source hash, score, grade, counts and review flag.
  8. The attester holds ATTESTER_ROLE on the registry.

Onchain and offchain

Onchain (Base Sepolia)Offchain
Grade, score, tier, counts, review flag, timestamp, revoked flagThe report JSON and Markdown
sourceHash, reportHash, receiptsRoot, reportCidYour source code, unless it is verified source of a deployed contract
The NFT, its metadata and SVGPrompts 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.