Mithra
Specification

One signed intent, carried from the payment through the enclave into the verdict.

Payment, execution and reputation are each solved for agents; nothing connects them. What follows is the whole connection — every preimage, every recovery, and the one link we could not close. It is written to be checked against the code, not to be believed.

01

What gets signed

Alice commits to the job before anyone is paid and before any work starts. The commitment is a single hash over the brief, the data, the constraints, the price and a nonce — so a job differing in any one of those is a different hash.

intentHash = keccak256(
  abi.encode(briefHash, dataHash, constraints, price, nonce)
)

She signs that hash with EIP-712. The contract later recovers her own signer and compares it against the registered client — it never trusts an intent-and-output pair that Bob hands it, because Bob has every reason to hand it a matching pair.

The brief and the data themselves travel ECIES-encrypted to Bob's registered public key. What is public is the commitment; what is private is the job.

02

The two signatures

Both are ecrecover-compatible, and they prove different things. Confusing them is the easiest way to overstate this project, so they are stated separately.

A · 0G's TEE signature — a genuine enclave produced this output

Measured, not assumed: the signed message is not the output text. It is a colon-joined tuple in which the output appears as the sha256 of the raw response body.

"<h1>:<sha256(raw response body)>:<ProviderType>:<ProviderIdentity>:<h3>"

verify:  recoverAddress(hashMessage(text), signature) === teeSignerAddress

So the output is covered — as the fingerprint of the body containing it, not as plain text. Same guarantee against tampering; a different sentence, and we use the accurate one. A one-byte change to the body drops out of the tuple. Hash the raw bytes: a JSON re-stringify depends on key order and would break silently the day the provider reorders a field.

Two further conditions the code enforces rather than assumes: the chat id comes from the ZG-Res-Key response header, and teeSignerAcknowledged must be true — a TeeML provider whose signer the contract owner has not vouched for is the provider's own claim, and we refuse to verify against it.

B · The binding signature — this code checked the match

preimage  = keccak256("agentId|sealId|timestamp|hex(sha256(body))")
signature = 64-byte R‖S, no EIP-191 prefix — v is discarded by the wrapper
            → brute-force v ∈ {27,28} off-chain, pass v into the contract

The body it signs must be byte-stable, because raw bytes are what get hashed; nothing is re-stringified before verification.

03

The on-chain verdict

Verifier.sol on Base Sepolia is the only thing that decides. It does three checks and refuses the job unless all three pass:

1.  reconstruct the binding preimage, recover the signer
    require signer == enclaveSignerOf[agentId]

2.  recover Alice's EIP-712 signer
    require signer == registeredClient

3.  require match == true

→   emit JobVerified(intentHash, outputHash)

A job that fails is not silently dropped — it emits a rejection with a reason, and the subgraph indexes those too, so a cheating attempt stays visible exactly where hiring decisions are made. The four ways Bob can cheat, and what each one trips:

substitute   he answers a different job     → MatchFalse
tamper       he edits the input            → MatchFalse
forge        he rewrites the signed body   → BadEnclaveSig
selfintent   he supplies his own intent    → BadClientSig
04

The binding we shipped

This is the section to read if you only read one. Bob's binding — the code that recomputes the hash and decides match — runs on an ordinary host, not in an attested enclave. We had no TDX machine and 0G does not host that execution for us.

Rather than fake an attestation, we moved one end of the binding inside 0G's real enclave:

Alice signs intentHash
  → it is placed at the TOP of the prompt
  → the model copies it verbatim into its answer
  → 0G's TEE signs sha256(response body), which CONTAINS that answer

⇒ a genuine 0G enclave has attested:
  "a response carrying THIS intentHash was produced in here"

Bob cannot forge that first link on his own machine — it comes from 0G hardware. The echo is checked against the raw output by our own code, not by the compute backend, because a backend could simply claim it inserted the commitment. Verification is exact-match on the full 64-hex value: one shifted character breaks it, and "close enough" is not a category. Measured 5/5 verbatim.

What that does not give you: the match check itself is computed by unattested code. Alice can verify it independently; a stranger cannot. attestation: 'none' and imageHash: null stay in the code and in the UI until that changes. The honest sentence is: 0G attests the compute and carries the intent through it; the match check is client-verifiable, not yet third-party-verifiable.

05

Money, and its ordering

Bob answers HTTP 402 until he is paid, and does no work before the authorisation exists. But the authorisation is not the payment: money moves only after the contract has ruled.

Alice authorizes x402      → nothing has moved yet
Verifier emits JobVerified → the job is ruled valid
Bob calls /settle          → the guard RE-READS that receipt on chain
                             for THIS intentHash, then releases

The guard is structural rather than a convention each backend is trusted to follow. Checking merely that a transaction hash was supplied would let Bob invent one; reading the event makes that impossible. A rejected job never reaches the settle line, and the signed authorisation is never submitted.

Two rails, and the receipt reports which one you got rather than asserting it:

base-stealth   paidTo is a fresh ERC-5564 address, different every job
               paidTo ≠ agentIdentity — the payment record does not name Bob

hedera-x402    paidTo == agentIdentity, the account published in his agent card
               this rail buys autonomy, not privacy, and says so

Two addresses a reader can compare beat a private: true flag they would have to take our word for. The limit, stated before someone works it out unaided: on this testnet there is one client and one agent, and JobVerified names the agentId, so timing correlation still links a payout to Bob. What the stealth address hides is the payment record, not the whole system.

06

The timeline

Every stage — quote, intent, enclave, output, settlement — is committed to a Hedera Consensus Service topic, including the rejections. The timestamps shown in the demo are Hedera's own consensus timestamps, read back from the public mirror node; a timeline whose times come from the machine that wrote it proves nothing.

What goes on the topic is commitments only — hashes, stage names, flags. The brief, the data and the output never go near it, and the code scans its own outgoing messages for those secrets before they reach the network.

07

Check it yourself

Nothing here needs our word for it. Each of these is a public address on a public testnet.

Verifier · Base Sepolia0x3B116D648B710f551e37223c4c4d39879AFEEb96
ERC-8004 registry · Base Sepolia0x8004A818BFB912233c491871b3d84c89A494BD9e
Agent idsBob 8429 · Alice 8431
0G provider · Galileo, chainId 166020xa48f01287233509FD694a22Bf840225062E67836
Model · attestationqwen/qwen2.5-omni-7b · TeeML
0G TEE signer0x83df4B8EbA7c0B3B740019b8c9a77ffF77D508cF
Hedera timeline topic0.0.9738448
Subgraphconfidential-agents (The Graph Studio)

Or run it. Every phase has a gate that proves it against live networks, and the recorded evidence is checked in so the gates cost no faucet credit.

pnpm demo:base                    a full job, no payment rail
pnpm demo:base -- --fraud substitute
                                  rejected on chain, nothing settles

pnpm gate:P3-D    an honest run verifies; three fraud modes are
                  rejected ON CHAIN — MatchFalse, BadEnclaveSig, BadClientSig
pnpm gate:P1-D    the fourth, tamper, which shares the MatchFalse path
pnpm measure:e2e  re-measure end-to-end latency

Measured end to end over 5 runs with no payment rail: p50 21.0 s, p95 22.7 s against a 60 s budget. The dominant term is the 0G inference call, about 12.4 s.

08

What we do not claim

A spec that only lists what works is a brochure. These are the limits we hold ourselves to, and we would rather say them than have them found.

  • Not Sybil-proof reputation
    Feedback anchored to paid, verified jobs. Inflating it is expensive, not impossible.
  • Not a solution to prompt injection
    The binding catches task substitution and input tampering. That is the whole claim.
  • Not two TEEs
    One, and 0G built it. Bob's binding runs on an ordinary host — see §04.
  • Not decentralized compute
    The provider we pinned reports ProviderType: centralized, ProviderIdentity: aliyun. The TEE seal is real Intel TDX; the operator is a single cloud.
  • Not private on Hedera
    The Hedera run buys autonomy. Recipient privacy is the Base run's stealth rail.
  • Not first
    Confidential agent payments shipped before us. The intent binding is what is ours.
  • Not “the AI decided, therefore it is trustworthy”
    The agents' brain is unattested and outside every guarantee here. It can refuse to trade; it cannot overpay, invent a counterparty, or make an invalid job verify. If the brain and the contract disagree, the contract is the one that decided.
  • Not “Claude runs the analysis”
    Claude decides; 0G Sealed Inference produces the deliverable. Swapping the brain cannot change one byte of what the enclave signed.
Watch it runRead the source