Skip to main content
Every verification produces a receipt: a hash-chained transcript of the run, the judge key’s signature over the transcript root, and, when anchoring succeeds, an onchain record of what was signed.

Transcript

The transcript is a list of JSON lines, each {seq, at, type, data}. The lines form an append-only keccak256 chain:
Changing, removing or reordering any line changes the root. outputHash is keccak256 of the judge’s full output text.

Signature

The judge key is a secp256k1 key. It signs a 32-byte digest with the EIP-191 personal-message prefix. The digest depends on where the record anchors:
  • Base: keccak256(abi.encode(uint256 chainId, address registry, bytes32 runId, bytes32 transcriptRoot, bytes32 outputHash, bytes32 hostdata)).
  • Starknet: keccak256(abi.encode(uint256 chainIdFelt, uint256 registry, uint256 commitment)), where commitment is a Poseidon hash over 'CHANCE_RUN_V1' and the 128-bit halves of runId, transcriptRoot, outputHash and hostdata. See Private records.
runId is the value returned as requestHash on a verification. hostdata is the enclave’s code measurement, or 32 zero bytes when the run was not attested.

Attestation

When the judge runs in an attested enclave, the model calls, the transcript chain and the signing all happen inside an AMD SEV-SNP confidential VM. The key carries a Microsoft Azure Attestation token whose x-ms-sevsnpvm-hostdata claim is the code measurement. That measurement is bound into the signed digest and into the onchain record, and the token’s hash is committed in run.attested. attested is set per run. true means the signing key’s attestation mode was Azure SEV-SNP. false means the run was signed outside an attested enclave. The receipt then still proves the transcript’s integrity and which key signed it, but not the environment that ran the judge. GET /api/status shows the current judge address and attestation mode.

Anchoring

After signing, the commitments are written onchain:
  • Base: RunRegistry.recordRun(runId, transcriptRoot, outputHash, hostdata, uri, signature). The registry rejects signatures from keys that are not registered judges. uri is the transcript URL, which only the owner can open.
  • Starknet: the private registry. See Private records.
Anchoring never fails a run. If it is skipped (no registry configured) or the transaction fails, the verdict and signature are still returned and anchorTx is null. After a successful anchor, the harness polls the registry for about 10 seconds to confirm the record.

Receipt fields

POST /api/v1/intent (201) and verify_intent return proof with:
string | null
Head of the transcript chain.
string | null
keccak256 of the judge’s output text.
string | null
The judge’s EIP-191 signature over the digest.
string | null
Address of the signing key.
boolean
Whether the key’s attestation mode was Azure SEV-SNP.
string | null
Anchor transaction hash, or null when anchoring was skipped or failed.
boolean
Whether the registry confirmed the record.
string
https://harness.chance.cc/api/runs/{id}/transcript. Owner-only.
string | null
Explorer link for anchorTx: basescan.org for Base, voyager.online for Starknet.
GET /api/v1/intent/{id} returns transcriptRoot, outputHash, signature, judge, attested and anchorTx, plus anchorStatus in place of anchorVerified, transcriptUri and explorerTx: The MCP get_verification tool returns the same fields as the GET, plus transcriptUri and explorerTx. hostdata and the digest are not in any API response. They are in the transcript JSON.

Check a receipt

As the owner. Open https://harness.chance.cc/verify/{id} signed in as the account that ran the verification. {id} is the path segment of transcriptUri between /api/runs/ and /transcript. The page loads the transcript and, in your browser:
  1. Verifies the attestation token’s RS256 signature against the issuer’s published keys, as of the time the transcript committed it. Checks that it attests a SEV-SNP VM that is not debuggable, that hostdata is a published Chance enclave build, that the token names this judge, and that its hash matches run.attested.
  2. Recomputes the transcript root from the lines.
  3. Checks keccak256 of the output against outputHash.
  4. Recovers the signer from the digest and signature and compares it with judge.
It then asks the Chance server whether the registry holds the record. From code, GET {transcriptUri} with your API key returns the same transcript JSON. As a third party. Transcripts are owner-only: the URL is published in the onchain record, so the URL alone opens nothing. The owner downloads the JSON from the verify page and shares it. The recipient can check it without Chance:
  1. Recompute the root from lines and compare it with transcriptRoot.
  2. Compare keccak256 of output with outputHash.
  3. Rebuild the digest from chainId, registry, runId, transcriptRoot, outputHash and hostdata, recover the signer from signature, and compare it with judge.
  4. On the Base registry, call judges(judge) and verifyRun(runId, transcriptRoot, outputHash, hostdata). Both should return true.
  5. If attestation.maaToken is present, verify its RS256 signature against the keys at {iss}/certs, check that x-ms-sevsnpvm-is-debuggable is false, and compare x-ms-sevsnpvm-hostdata with hostdata.
For a record anchored on Starknet (anchorChain: "starknet"), steps 3 and 4 use the Starknet digest, with starknetChainId, starknetRegistry and commitment from the same JSON. GET /api/status needs no authentication and returns the registry address, chain id, current judge address, attestation mode, model provider and model.

What a receipt does not prove

  • That the decision was right. It proves what was judged, what the judge said, and which knowledge it used.
  • That the input was true. A wrong intent or wrong context is faithfully recorded and judged as given.
  • That the provider served the model it reported. agent.model_call records the served model as the provider reported it.