Compute-Integrity · OCG §18

Proving a financial calculation ran the way it claims

A §18 proof is a small piece of cryptography attached to a result. It says the published code, given these inputs, produced this exact output. You can check that in a few milliseconds, on your own machine, with no network, without re-running the calculation, and without taking the operator's word for it. If you need to, the proof can confirm the output while keeping the inputs private.

This page explains why that matters for regulated financial work, walks through how the proof is built and checked one step at a time with the math and code for each step, and tells you what we got wrong on the way so your team can skip the same mistakes.

01 · Why finance needs this

A hash proves two results match. It does not prove either one is honest.

Every regulated decision has to be defensible later: a capital calculation, a sanctions screen, an IFRS 17 measurement, a margin or settlement instruction. Today, to trust someone else's number you either re-run their tool and trust your own copy of it, or you accept an attestation and trust their controls. OpenChainGraph already gives every result an execution_hash, a SHA-256 over the canonical inputs and outputs:

the existing hash
execution_hash = SHA-256( JCS({ policy_parameters, output_payload }) ) // JCS = RFC 8785 JSON canonicalization (stable bytes) // equal hashes => equal outputs // but silent on: WHICH code produced them, and whether it ran honestly on the stated inputs

That hash lets two parties confirm their outputs are identical. It does not tell you which code produced the number, or that the code ran faithfully on the inputs it claims. In finance that gap is expensive and concrete. Auditors re-perform calculations by hand. Counterparties stand up duplicate infrastructure just to reconcile. Supervisors accept SOC reports and management attestations they cannot independently check. The trust is social, not mathematical.

A compute-integrity proof closes that gap. Here is what it buys you, specifically in a regulated setting:

02 · How it works, step by step

From a calculation to a checkable proof

The mechanism is a zero-knowledge virtual machine (a zkVM): a processor that runs a program and emits a proof that it ran it correctly. The work is in feeding it the right thing to run and making the result reproducible. Here is the whole pipeline, with the math and code at each step.

Step 1: A deterministic kernel

Each tool's logic is a pure function. The same inputs always return the same output, with no clock, no randomness, no I/O.

kernel (the function behind a tool)
// example: a CET1 capital-ratio check export function compute(pp) { const ratio = pp.cet1_capital / pp.risk_weighted_assets; return { cet1_ratio: round6(ratio), breach: ratio < 0.045 }; }

Why it matters: a proof of a computation is only meaningful if the computation is reproducible in the first place. Determinism is the foundation everything else sits on.

Step 2: Fingerprint the source (§17)

Take the SHA-256 of the kernel's source code. That hash is the kernel's identity, and it is published next to the tool so "the published model" becomes a precise, checkable claim rather than a label.

§17 kernel identity
kernel_digest = SHA-256( utf8( source_of(compute) ) ) // e.g. sha256:a3f1c2... // published in chaingraph.json next to the tool: "compute_images": [ { "system": "sha256-source", "image_id": "sha256:a3f1c2..." }, // the JS source { "system": "risc0", "image_id": "sha256:a1a0bc89..." } // the guest, step 4 ]

Step 3: Run it inside one universal guest

The guest is a small program compiled for the zkVM's instruction set (RISC-V, RV64IM). It embeds a JavaScript engine (QuickJS), takes the kernel source and the inputs, runs compute, and writes three things to its public output, the journal.

universal guest, RV64IM, commits the journal
// ONE guest proves ANY kernel. The kernel source is an INPUT, never baked in. function guest(kernel_source, policy_parameters) { const compute = evalInQuickJS(kernel_source); // QuickJS, compiled to RV64IM commit({ // -> the public journal: kernel_digest: SHA-256(kernel_source), // which code ran (= §17) output: compute(policy_parameters),// what it produced chaingraph_version: "0.4.0" }); }

Why it matters: because the kernel source is an input rather than baked in, one guest covers every kernel, present and future. We do not maintain a separate proving program per tool.

Step 4: Build the guest reproducibly to get an ImageID

Compiling the guest with a pinned toolchain produces a deterministic fingerprint of the program, the ImageID. Publish it.

imageid
ImageID = digest( compiled_guest_method_image ) // the RV64IM ELF, pinned toolchain reproducible build => same ImageID => one image covers every kernel

Why it matters: anyone can rebuild the guest from public source and get the same ImageID. That is how a third party confirms exactly what program runs their data, trusting no one.

Step 5: Prove it, off-line

A prover runs the guest in the zkVM and produces a STARK receipt, then wraps it into a Groth16 proof over the BN254 curve, about 200 bytes. This is heavy and happens out of band, never in a browser and never on a request path. Always with development mode off, so the receipt is real.

prove (RISC0_DEV_MODE=0)
STARK receipt --wrap--> Groth16 over BN254 (~200 bytes) proof pi = ( A in G1, B in G2, C in G1 ) verify e(A, B) = e(alpha, beta) · e( vk_x, gamma ) · e(C, delta) public inputs encoded in vk_x: ImageID and SHA-256(journal)

Why it matters: a small Groth16 proof verifies with a couple of pairings, so checking is cheap, and keeping proving off-line means the live service stays fast and never holds private data.

Step 6: Attach the proof, leave the hash alone

The receipt rides inside the result's artifact and is excluded from the execution_hash preimage.

artifact (excerpt)
"audit_signature": { "compute_proof": { "receiptFormat": "groth16-bn254", "imageId": "sha256:a1a0bc89...", "journal": { "kernel_digest": "sha256:a3f1c2...", "output": { "cet1_ratio": 0.123456, "breach": false }, "chaingraph_version": "0.4.0" }, "seal": "0x... (~200-byte Groth16 proof)" } } // execution_hash is taken over { policy_parameters, output_payload } ONLY, // so attaching a proof never changes it. Existing hashes and chains stay valid.

Step 7: Verify it, anywhere

A self-contained pairing check confirms the proof against the published ImageID and the journal. No network, no toolchain, milliseconds. This is the part a regulator or counterparty runs.

verify (offline, no toolchain)
import { verifySeal } from "./kernels/_computeproof.mjs"; // @noble/curves bn254, zero deps const ok = await verifySeal( artifact.audit_signature.compute_proof, PUBLISHED_IMAGE_ID // from chaingraph.json compute_images[] ); // ok === true iff the published guest produced exactly this output

That is the trust model in one line: rebuild the guest and you confirm what code runs; check the proof and you confirm it ran that code on those inputs to get that output. No hardware enclave, no consensus, no operator to trust.

03 · What we learned building it

What worked, what did not, and what surprised us

We took the AINumbers suite from zero §18 coverage to a real proof on every clean kernel. None of it was faked (more on that below). Here is the honest account, so you can plan around the same problems instead of discovering them.

One universal guest beats porting every kernel

The obvious first idea is to rewrite each kernel in the zkVM's native language and prove that. We considered it and walked away. A separate guest per kernel means a separate thing to maintain, and every one is a fresh chance to drift from the JavaScript the browser actually runs. Putting one JavaScript engine in the guest and passing the kernel source as input gave us a single program, a single ImageID, and no per-tool proving code. If you adopt this, do the same: one guest, source as input.

Floating point is what bit us, and it bit late

The arithmetic standard (IEEE-754) only guarantees the same bits across engines for the basic operations. The library functions do not carry that guarantee, and they can differ in the last unit in the last place between two correct engines. A handful of our kernels (the machine-learning and quantitative ones) produced guest output that differed from the browser by exactly that last bit, and a last-bit difference means the proof does not match the canonical result.

Math.exp(0.5) // V8 vs QuickJS-on-RV64IM (soft-float): can differ in the last ULP // IEEE-754 reproducible across engines: + - * / sqrt (and fma) // NOT reproducible: exp log pow sin cos hypot (the transcendental set) import { exp } from "./_detmath.bundle.mjs"; // identical bits in browser, server, AND guest

The fix was a shared deterministic math library, used identically in all three places. Those roughly twelve transcendental kernels were the last and hardest part of the entire effort. If your kernels do pricing, risk, or ML math, budget for this first, not last.

Some faithful code is simply too expensive to prove

One kernel validated URLs with a spec-faithful parser. Inside the guest, that cost roughly 5.4 billion cycles, which is effectively un-provable at any sane price. We replaced it with a small deterministic check and used that same check on every path, so the output never diverged.

faithful WHATWG URL parser, in-guest ~ 5.4 x 10^9 cycles (un-provable) deterministic isHttpsUrl, used everywhere ~ 6.9 x 10^6 cycles (~800x cheaper, identical output)

The lesson generalizes: faithful-inside-the-guest is sometimes the wrong target. The real requirement is deterministic and identical everywhere, and a simpler function that meets it beats an expensive one that does not.

Non-determinism hides in boring places

Two more classes of bug had nothing to do with math. One kernel imported a helper module lazily, which let execution order vary; making the import eager fixed it. Another formatted numbers with the locale-aware formatter, which quietly picked up the host's locale and changed the output.

(1234.5).toLocaleString() // "1,234.5" or "1.234,5" depending on host ICU locale (non-deterministic) fmtEnUS(1234.5) // "1,234.5" always, fixed and deterministic everywhere

Neither looked like a determinism problem at a glance. The takeaway is that determinism is a property of the whole runtime, the imports and the formatting and the locale, not just the arithmetic.

Never let development mode lie

The prover has a development mode (RISC0_DEV_MODE=1) that emits a fake receipt which still passes verification. A fake proof sitting in a standards repository is the worst outcome we could ship, worse than shipping no proof at all, because it looks real. We ran every single proof with RISC0_DEV_MODE=0, and the rule we held to was simple: if we could not produce a real proof, we shipped nothing for that kernel and said so. Hold the same line.

What it actually costs, so you can set expectations

The full suite proved on a single consumer GPU (an RTX 3080) in about five and a half hours. The Groth16 wrap ran roughly 252 seconds per kernel. The first toolchain build took 5 to 15 minutes. Verifying any of the proofs is milliseconds. Proving is the investment and it is a one-time, off-line cost; verifying is free and universal. If you have no GPU, a proving network (such as Boundless or Succinct) is an option for the proving step, but keep it out of the verify path.

Triage before you prove

Before generating a single proof, sort your kernels into three buckets: the ones using only basic arithmetic are ready immediately, the ones using integer powers need a quick check that the exponent really is an integer, and the transcendental ones need the deterministic math work first. That sort is your project plan. We classified all of ours up front, and it told us exactly where the hard work was.

04 · How it compares

Reproducible determinism, not hardware or consensus

Most verifiable-compute approaches anchor trust in one of two places. OCG §18 uses neither in the verify path.

ApproachWhat you have to trustOCG §18
Secure hardware (TEE, confidential compute)A chip vendor's attestation and supply chainNo enclave. Trust comes from the reproducible re-execution and the proof.
Blockchain or token networkA consensus set and its economic incentivesNo chain in the verify path. Verification is a local pairing check.
Operator attestation (SOC reports)The operator's controls and an auditor's review of themNo operator trust. The proof binds the published code to this output.

Proving can use heavy compute, and a proving network is one way to get it. Verification stays self-contained, offline, and chain-free. That split is the whole promise: proving may be expensive and optional, checking is cheap and for everyone.

05 · Do it yourself

Verify a proof, or reproduce one

Verifying (no toolchain)

You need only the artifact, the published ImageID, and the verifier. Take any OCG artifact that carries a compute_proof, look up that tool's ImageID in chaingraph.json, and run the self-contained verifier. It does one pairing check and returns true only for a real proof of that exact output.

import { verifySeal } from "./kernels/_computeproof.mjs"; // @noble/curves bn254, zero deps const proof = artifact.audit_signature.compute_proof; const ok = await verifySeal(proof, PUBLISHED_IMAGE_ID); // ok === true real proof of this exact output // ok === false tampered output, wrong journal, or wrong image

Proving, or confirming the ImageID (full toolchain)

If you want to generate proofs, or independently confirm that the published ImageID matches the published guest source, you run the prover. Rebuild the guest and confirm you get the same ImageID we published, then prove with development mode off.

prove it yourself (Linux or WSL, ideally a GPU)
curl -L https://risczero.com/install | bash && rzup install # Rust + cargo-risczero + r0vm cargo build --release # rebuild the guest, then r0vm --id # confirm the SAME ImageID -> sha256:a1a0bc89... RISC0_DEV_MODE=0 cargo run --release # real proof: STARK -> groth16-bn254 (never dev mode)

Then check that the journal's output equals what the JavaScript kernel produces for the same inputs, and verify your own receipt with the same self-contained verifier. The exact field definitions are in the technical spec under §17 and §18.

The keystone is the ImageID. Rebuilding the guest from public source and getting the same fingerprint we published is what turns "trust our attestation" into "check it yourself." Everything else follows from that one reproducible build.
06 · Limits

Where to be straight about it

Sources

References

OpenChainGraph Standard §17 (kernel identity) and §18 (compute-integrity), in the technical spec. RISC Zero receipts (journal and seal). Ethereum Foundation zkVM Standards v0 (RV64IM). Software provenance by running an interpreter in a zkVM (arXiv 2026). Floating point in zero-knowledge systems. QuickJS. RFC 8785 (JSON Canonicalization Scheme).