How a private review becomes a public receipt.
Seven explainers — one per moving part of the system. Read them in any order. The home page links here from each module card; jump straight to the section you came for.
Four small lights tell you what was checked.
Every receipt page renders the same four-pill row. Each pill goes green only when the corresponding check passes against live data, not against a cached flag. The same row sits next to the Run button before submission: pending, then in-progress, then verified or mismatch.
Storage
0G StorageGreen when: The receipt body has been fetched back from 0G Storage and its keccak256 matches the receiptRoot recorded inside the receipt itself.
Amber when: The blob is not reachable, or the bytes returned by the indexer do not hash to the receiptRoot. Either is a tamper signal worth surfacing.
Compute
0G Compute providerGreen when: The inference ran on a 0G Compute provider whose router_flag was set, or whose attestation was confirmed post-hoc through broker.processResponse.
Amber when: The run used an external provider (NVIDIA NIM · OpenAI · local Ollama). The output is still signed and chain-anchored, but it is not TEE-attested. We render this amber by design.
TEE
Provider TEE attestationGreen when: The verificationMethod field is one of router_flag or compute_sdk_process_response. Re-running the broker check from a separate machine returns the same attestation.
Amber when: verificationMethod is external-signed. The plaintext was visible to the operator of whichever model served the request.
Chain
0G Chain registryGreen when: A read of the on-chain registry at chainAnchor.registryAddress confirms the receiptRoot is stored against chainAnchor.onChainId, signed by the recovered agent.ownerWallet.
Amber when: The on-chain row does not match the off-chain JSON. Usually means the receipt JSON was edited after anchoring, or the wrong receipt id was looked up.
Four locations. Different visibility at each.
A receipt is the record of a transit. The plaintext moves from your browser into a hardware enclave, then leaves only as ciphertext and as a signed summary. Each ring is a different trust boundary; reading them inward, sensitivity rises and audience shrinks.
RING 01Your browserContent: Plaintext of the document, plus the session key used to encrypt it.
Reads: You. The key is generated client-side and never written to disk.
RING 02A 0G Compute TEEContent: Plaintext, for the duration of the inference run only.
Reads: The TEE itself. The provider operator does not see the inside of the enclave.
RING 030G StorageContent: The encrypted blob (when the run wrote one) plus the signed receipt JSON.
Reads: Anyone with the storage root can fetch the ciphertext; only key-holders can decrypt.
RING 040G ChainContent: The receiptRoot, the agent address, the receipt type code, the timestamp.
Reads: Everyone. The chain is the part designed to be publicly readable.
After the run, ring one is empty. The session key is zeroed in memory and its fingerprint is the only artifact retained. See §06 — Burn mode for what that fingerprint proves and what it cannot.
Two tiers. Both are honest. One is stronger.
Not every model lives behind a TEE today. We sign and anchor anyway, but we draw the line in the receipt itself. The single field verificationMethod decides which tier renders, and the colour follows from the value — green for TIER 1, amber for TIER 2. The page refuses to render any other combination.
TEE-verified
- verificationMethod
router_flag ·compute_sdk_process_response- Provider
- 0G Compute provider
- Plaintext visible to
- The provider TEE only. Operator-side disclosure is out of scope.
- Independent replay
- Re-run broker.processResponse against the recorded provider address on any machine.
External-signed
- verificationMethod
external-signed- Provider
- NVIDIA NIM · OpenAI · local Ollama
- Plaintext visible to
- The provider operator. We do not pretend otherwise.
- Independent replay
- Signature recovery and chain anchor still verify. The TEE chip stays grey.
TIER 2 exists because some skills do not yet have a TEE-hosted equivalent, and we would rather ship a signed amber receipt than no receipt at all. The receipt page surfaces the tier in three places: the header chip, the four-light TEE pill, and the model attribution block. A judge can read any of the three and reach the same conclusion.
Honest amber beats a green light that does not check out.
One schema. Thirteen slots. One canonical hash.
A receipt is a JSON object validated against a single Zod schema. The type field picks one of thirteen slot codes; the chain rejects anything else. Across all slots, the recipe for the on-chain identity of the receipt is the same: a deterministic byte serialization, hashed with keccak256, signed by the agent wallet.
The reason this list is in this order is that step three — RFC-8785 JCS — pins the byte sequence across languages. A verifier written in Go or Rust reaches the same receiptRoot from the same JSON. That property is what lets a third-party auditor run their own verification without our code.
The thirteen slots
Slot codes are part of the on-chain row. The slot was promoted from 10 to 13 by ReceiptRegistryV3 on testnet; V2 admits 0-9, V1 admits 0-9.
doc_askslot 0auditslot 1consensusslot 2burnslot 3memory_accessslot 4skill_execslot 5code_changeslot 6passport_updateslot 7swarmslot 8subscription_skill_execslot 9doc_room_createslot 10doc_room_readslot 11memory_consolidationslot 12Today the chain holds 44 anchored receipts and 4 minted agent passports. Both numbers are read from the public chain at the moment this page loaded; no cache, no synthetic data.
For the full field-by-field map and the slot mapping across V1, V2, and V3, see docs/RECEIPT_SCHEMA.md.
One role, three, five, or six. Pick the depth the work deserves.
Consensus is monotone: each higher tier strictly extends the one below it. The judge role is always last. The composition is set in code, not in a manifest field a skill author can fudge.
Convergence between roles is scored with a tokenized Jaccard plus an embedding-cosine pass. Below a default threshold of 0.6, the receipt records "no convergence" rather than a fake agreement. A skill manifest can raise the threshold for the high-stakes and audit tiers via og.consensus.threshold.
The session key dies at the end of the run.
Burn mode is a specific, narrow promise: at the end of an inference run, the symmetric key that encrypted the document is overwritten with zeros, and its sha256 fingerprint is captured beforehand so the receipt can attest the destruction. The blob on 0G Storage stays but is no longer decryptable by anyone, including us.
What burn mode defends
- Operator-side disclosure after the run. A subpoena to us cannot produce plaintext we no longer hold the key for.
- Long-tail re-reads. A storage-layer breach in 2028 returns ciphertext nobody can decrypt.
- Vendor lock-in panic. The receipt is intact even if our service is offline; the document under it is unreadable.
What burn mode does not defend
- A compromise of your local machine before or during the run. Plaintext sits in your browser memory while you draft the prompt.
- A side channel inside the TEE itself. Burn mode is upstream of the enclave guarantees; if the enclave leaks, burn mode does not patch that.
- A copy you made and stored elsewhere. Burn mode acts on the canonical blob, not on screenshots you took.
The cryptographic invariants
- AES-256-GCM. 256-bit session key from
crypto.randomBytes(32). - 96-bit GCM nonce from
crypto.randomBytes(12). Never derived from time or content. - Blob layout:
nonce (12) ‖ ciphertext ‖ tag (16). Self-contained. - Fingerprint
sha256(key)is captured before the buffer is zeroed. The order matters. - The fingerprint, the destruction timestamp, and the algorithm tag land in the receipt's
burnblock.
Source: packages/og-storage/src/burn.ts. Read the threat-model JSDoc; it is the canonical statement.
Hard questions, short answers.
A receipt says "FULLY VERIFIED". What was actually verified?
Does the chain see my document?
Why is some receipt amber and not green?
Can I verify a receipt without trusting your code?
What happens when burn mode runs and the storage indexer is later offline?
What is a passport, and why do I need one?
How is this different from a vendor data room?
Glossary
- receiptRoot
- The keccak256 of the canonical JCS-serialized receipt body (minus signature, id, chainAnchor). The signature is over this value. The on-chain anchor stores this value.
- verificationMethod
- The single field that picks TIER 1 (router_flag or compute_sdk_process_response) vs TIER 2 (external-signed). Lives at teeVerification.verificationMethod.
- agent.ownerWallet
- The EVM address recovered from the receipt signature. Must match the wallet that owns the passport at agent.passportId.
- keyFingerprint
- sha256 of the burn-mode session key, captured before the key buffer is zeroed. Lives at burn.sessionKeyFingerprint.
- convergence
- A tokenized Jaccard plus embedding-cosine score across consensus roles. Below 0.6 by default, the receipt records "no convergence" rather than a fake agreement.
- four-light row
- The Storage · Compute · TEE · Chain pill set. Each pill is verified, pending, mismatch, or amber. Renders on the run panel and on every receipt page.
- TEE-independent
- A second-machine re-run of broker.processResponse against the recorded 0G Compute provider. Available via "ivaronix receipt verify <id> --tee-independent".