Read-Only Recipe · No Wallet · No Gas

Verify a compute-integrity receipt on-chain, with one eth_call

Every AINumbers tool that carries a compute proof publishes a risc0 zkVM receipt: a Groth16 seal over the claimed journal output. RISC Zero deploys an immutable Groth16 verifier behind a VerifierRouter on Ethereum mainnet and several L2s. This page is the complete recipe for checking one of our receipts against that deployed contract from your own machine, spending nothing, signing nothing, and trusting no code we wrote: the cryptography runs in the deployed contract, and the answer is a yes or a revert. The same seal is also verified directly in the browser: the Agent Work Ledger now runs the in-house pure-JS BN254 Groth16 reference verifier over each receipt locally (parity-gated against the kernel-side verifier), so the on-chain eth_call below serves as the independent second opinion alongside that in-page cryptographic check.

eth_callread-only, no transaction
0 gas · 0 keysnothing to sign or spend
3 inputsseal + imageId + journal digest
~1 sone round trip to a public RPC

§1What you are holding

A published receipt is a JSON object shaped by the OpenChainGraph standard, §18 (compute-integrity proof). The fields that matter on-chain:

{
  "type": "ZkVmReceipt",
  "system": "risc0",
  "receiptFormat": "groth16-bn254",
  "imageId": "sha256:93c746e79afcf4b27f6d2a6da6cd142a4dd0da31f33d942f92b97344188526c5",
  "seal": "JprnquQV+W8PZGrVb3psyDFR3XLhdjfIXqv6BPQl4toS…",   // base64, 256 bytes
  "journal": { …the committed output object… }
}

The journal byte contract (standard §18.7)

The on-chain verifier does not take the journal object; it takes its SHA-256 digest. The digest is defined over the RFC 8785 (JCS) canonical serialization of the journal object, encoded UTF-8: compact, keys sorted at every nesting depth, no insignificant whitespace. That byte string is what the guest committed, so it is the only serialization a genuine seal verifies against. Hash anything else and an honest receipt fails, which is a feature: it is how you know you hashed the right bytes.

§2Derive the three on-chain inputs

2.1 journalDigest

Canonicalize the journal per RFC 8785, encode UTF-8, SHA-256 it. Two dependency-free ways:

2.2 imageId

Strip the sha256: prefix; the remaining 32-byte hex is the on-chain value.

2.3 seal, with the selector prefix

Base64-decode the seal to 256 raw bytes. The on-chain call needs the 4-byte verifier selector in front: 73c457ba for the risc0 v3.0.x Groth16 parameters our receipts are proven under. So the bytes argument is 260 bytes: 73 45 c7 ba followed by the decoded seal, hex-encoded for the RPC.

Where the selector comes from

The selector is derived from the verifier parameters (the risc0 control root, the BN254 control ID, and the Groth16 verifying-key digest), not from a crate version string. It is computed as bytes4(sha256(tag ‖ control_root ‖ bn254_control_id ‖ verifying_key_digest ‖ 0x0300)) under risc0's tagged-struct hashing. Our receipts are proven with cargo-risczero 3.0.5 against the default Groth16ReceiptVerifierParameters (control root a54dc85a…c1f56), the same parameter set risc0-ethereum shipped as its 3.0.0 verifier generation, whose selector is 0x73c457ba. Patch releases within the 3.0.x series did not change the circuits or the verifying key, so the selector, which commits to exactly those, is unchanged. Section 4 shows you how to confirm the routing live before you verify, with a one-word getVerifier call.

§3The calldata

The router exposes verify(bytes seal, bytes32 imageId, bytes32 journalDigest), whose ABI function selector is 0xab750e75 (keccak-256 of the signature, first 4 bytes). ABI-encode: offset word 0x60, then imageId, then journalDigest, then the seal length (0x104 = 260) and the zero-padded seal. Layout of the 420-byte payload:

OffsetBytesValue
0x004ab750e75 (verify function selector)
0x04320x…60 (offset of the dynamic bytes argument)
0x2432imageId
0x4432journalDigest
0x64320x104 (260, the seal length)
0x84260+28 pad73c457ba ‖ 256-byte Groth16 seal

Useful constants for reading responses:

§4Confirm routing, then verify

Optional but recommended first step: ask the router what verifier a selector maps to. Calldata is 3cadf449 plus the bytes4 73c457ba left-aligned in one 32-byte word:

curl -s -X POST https://ethereum-rpc.publicnode.com \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319","data":"0x3cadf44973c457ba00000000000000000000000000000000000000000000000000000000"},"latest"]}'
→ {"jsonrpc":"2.0","id":1,"result":"0x0000000000000000000000009f9994eb4cb5200198fefb470f8b50301662e696"}

A returned address means the selector is routed. A revert with SelectorUnknown means the router has no verifier for it (and SelectorRemoved means it was removed and can never be re-registered). Then the verification itself:

curl -s -X POST https://ethereum-rpc.publicnode.com \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319","data":"<CALLDATA>"},"latest"]}'
ChainRouterFree public RPC examples
Ethereum mainnet0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319https://ethereum-rpc.publicnode.com, https://eth.drpc.org
Base0x0b144e07a0826182b6b59788c34b32bfa86fb711https://base-rpc.publicnode.com, https://mainnet.base.org

Reading the answer:

§5Worked example: a published estate receipt

observed 2026-09-03 Receipt: chaingraph/kernels/fixtures/compute-proof/art-04-agent-identity-attestation-checker.receipt.json from the public repository. This is the one receipt in the published corpus whose journal carries unsorted nested keys, so it discriminates the JCS byte contract: canonical bytes and stored-order bytes genuinely differ for it.

imageId     = 0x93c746e79afcf4b27f6d2a6da6cd142a4dd0da31f33d942f92b97344188526c5
journalDigest (JCS) = 0x3a089ca010da4cc939c482b41b52b659a6ff177a2b82df91c64a774164bcd4be
selector     = 0x73c457ba
on-chain seal   = 0x73c457ba269ae7aae415f96f…505bc5b895e2e18 (260 bytes)

The full call, verbatim as run:

curl -s -X POST https://ethereum-rpc.publicnode.com \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319","data":"0xab750e75000000000000000000000000000000000000000000000000000000000000006093c746e79afcf4b27f6d2a6da6cd142a4dd0da31f33d942f92b97344188526c53a089ca010da4cc939c482b41b52b659a6ff177a2b82df91c64a774164bcd4be000000000000000000000000000000000000000000000000000000000000010473c457ba269ae7aae415f96f0f646ad56f7a6cc83151dd72e17637c85eabfa04f425e2da121193c6cc3010316f6f92d8c63e28f36022742d55ae0f840369eef960154de72898be54f9c57e5c91eb0eed5c27409437732fda9e6472b95f9f46a24036ff3e2b52d1d4208bf8b5c3b3839fa99da5caeacd483223f619a63a92d29a5489192c0cb98bcdc1bb9a0fddb62caf0c7f4fca70d2fc98a40aa604c9d982d0d2a865402f062a5c00f9e1d3319f7d61349398bfec179a0af8871ac32258382ed4688e2920c30556da14ee2eb1ba2e43038b5410d8d9f19506dfe2066bdd49ab6aa312c927fd595ad6ebb24b399f26fe0b4442401c414c5cbc1a5cbda505bc5b895e2e1800000000000000000000000000000000000000000000000000000000"},"latest"]}'
→ {"jsonrpc":"2.0","id":1,"result":"0x"}

Observed results, 2026-09-03 (four endpoints, two chains)

Ethereum mainnet via ethereum-rpc.publicnode.com and via eth.drpc.org: both returned "result":"0x" (verified). Base via base-rpc.publicnode.com and via mainnet.base.org: both returned "result":"0x" (verified). Chain IDs pinned on each endpoint before the calls: 0x1 for both mainnet RPCs, 0x2105 for both Base RPCs.

5.1 Tamper control: flip one byte of the seal

Same call with the final byte of the seal flipped from 0x18 to 0x19 (one bit):

→ {"jsonrpc":"2.0","id":1,"error":{"code":3,"message":"execution reverted","data":"0x439cc0cd"}}

5.2 Serialization control: the wrong journal bytes

Same seal, same imageId, but the journal digest computed over compact stored-insertion-order bytes instead of JCS canonical bytes (0xa92cceb5da47a360d4097c7585584aefac5036df9dd0d3be28b9cf0475243136):

→ {"jsonrpc":"2.0","id":1,"error":{"code":3,"message":"execution reverted","data":"0x439cc0cd"}}

Both directions on the one artifact that can separate them. The genuine receipt passes only under the canonical serialization, and the deployed contract agrees with the standard's §18.7 byte contract.

5.3 Reproducing a tool run from a URL

Every registered tool page also accepts a fragment-only deep link of the form #p=v1.<base64url(gzip(JSON policy_parameters))>, optionally followed by &run=1 to execute after prefill. The payload lives entirely in the URL fragment, so it is never transmitted to any server or written to any access log. The page decodes it with the same gzip codec the ledger page uses for #a=v1. artifact links, validates it against the tool's declared input schema, prefills the form, and (with run=1) runs the same computation an agent would reach through the tool's WebMCP registration, producing the identical execution_hash. Pastes larger than 30 KB compressed are rejected with the site's privacy banner and are never truncated or executed.

§6What this proves, and what it does not

A pass attests

That the 256-byte seal is a valid Groth16 proof, under the verifying key committed to by selector 0x73c457ba, of the risc0 ReceiptClaim built from your imageId and your journalDigest: an execution of that exact program image that halted cleanly with no assumptions, committing a journal whose SHA-256 is exactly the digest you supplied. The contract is the deployed, immutable risc0-ethereum verifier; the check is the same one an on-chain settlement would rely on.

A pass does NOT attest (read this part)

§7The RPC trust caveat

An eth_call is answered by whoever runs the RPC

Everything here is read-only: no keys, no funds, no transactions, nothing signed. But the verdict arrives as an RPC response, and a broken or hostile endpoint can fabricate "result":"0x" just as easily as it can serve a stale block. Treat any single RPC's answer as one witness. Minimum diligence: run the identical calldata against two independent RPC providers (different companies, different infrastructure) and require both to agree, pinning eth_chainId on each endpoint first so you know where you asked. For stronger assurance, run your own node, or move the same calldata into a real transaction you can observe in a block. The tamper controls in §5.1 and §5.2 are also worth re-running on your chosen endpoints: an endpoint that fails to reject the tampered seal is telling you something.

§8Addresses and further reading

dated observation 2026-09-03 Contract states are observations, and routers are admin-managed mappings; derive the live state rather than relying on this table's age.

WhatChainAddressRole
VerifierRouterEthereum mainnet0x8EaB2D97Dfce405A1692a21b3ff3A172d593D319selector routing, TimelockController-governed additions
VerifierRouterBase0x0b144e07a0826182b6b59788c34b32bfa86fb711same design
Routed verifier (selector 0x73c457ba)mainnet + Base0x9F9994Eb4Cb5200198FEfb470f8b50301662e696emergency-stop proxy fronting the immutable Groth16 verifier
Base Groth16 verifier behind the proxymainnet + Base + other chains0x2a098988600d87650Fb061FfAff08B97149Fa84Dstateless, immutable; carries the v3.0.x control root and verifying key

Design notes worth knowing: verifier implementations are immutable and stateless; each sits behind an emergency-stop proxy whose guardian can permanently disable it if a critical vulnerability is proven; a selector removed from a router can never be re-registered, so each selector maps to at most one implementation across time. The authoritative live list of routers and registered selectors is risc0-ethereum's contracts/deployment.toml on GitHub; our PR evidence for this page cross-checked that table against live getVerifier calls on both chains above.


This page is static documentation: it makes zero network requests, sets no cookies, and stores nothing. The curl commands above are for you to run from your own machine against your own choice of RPC. Recipe and copy by Post Oak Labs, CC BY 4.0. The underlying verifier contracts are RISC Zero's (risc0-ethereum, Apache-2.0 / GPL-3.0 as noted per contract); this page documents them and contains no risc0-ethereum source.