An AI agent paid for, and received, attested confidential inference — with on-chain proof. A live client ran the full loop against the deployed endpoint: detect → verify (Intel DCAP) → encrypt → pay (MPP session on Tempo) → decrypt. Captured 2026-06-18, Tempo Moderato testnet (chain
42431).
Result: ✅ PASS — the attestation gate cleared, an MPP payment channel opened on-chain
(SUCCESS), one response-chunk was metered and paid, and the agent decrypted a genuine
gpt-oss:20b answer that only the agent and the attested enclave ever saw in plaintext.
| Endpoint | https://mpprouter.onrender.com → POST /v1/chat/completions/stream |
| Network | Tempo Moderato testnet, chain 42431, currency pathUSD (TIP-20, 6 dp) |
| Payer (agent) | 0x9BD2B3C6dc9bDF069333EaeC42596E6d119C9f70 |
| Payee (service) | 0xa726a1CD723409074DF9108A2187cfA19899aCF8 (matches src/config.ts TEMPO_RECIPIENT) |
| Enclave | real Phala Intel TDX (teeType: INTEL-TDX-PHALA), DCAP UpToDate |
| Model | nosana:gpt-oss:20b (OSS model, inside the enclave) |
The agent fetched GET /tee/attestation and ran Intel DCAP. Every check passed; only then did
it construct the payer and sign a voucher. A failure here signs zero vouchers.
✅ real TDX enclave advertised (INTEL-TDX-PHALA + non-null quote) (teeType=INTEL-TDX-PHALA quote=present)
✅ DCAP quote authentic (Intel-signed)
✅ TCB up to date (status=UpToDate advisories=none)
✅ measurement (soft-pin: displayed) (mrtd=f06dfda6dce1cf90… rtmr=68102e7b524af310…)
✅✅ INDEPENDENTLY VERIFIED — genuine TDX, bound keys, signed receipt.
# post-pay receipt (binds the enclave keys to THIS ciphertext)
✅ quote report_data binds teePub+enclavePub
✅ enclave ed25519 signature valid
✅ sha256(tdxQuote) == encryptionProof.tdxQuoteHash (d2fdb8b8968ac68a… vs d2fdb8b8968ac68a…)
MPP session payments are two layers: a channel is opened on-chain (a deposit of
headroom), then each unit is metered off-chain with a signed voucher. Vouchers never hit
the chain; the deposit reclaims on close/timeout. Both layers are shown below honestly.
Tx hash: 0xaa213739980b114a516ac38588703be1cd72fa4ef96d1e9b98227ce990cb915b
Explorer: https://explore.testnet.tempo.xyz/tx/0xaa213739980b114a516ac38588703be1cd72fa4ef96d1e9b98227ce990cb915b
| field | value |
|---|---|
| block | 22788263 (0x15bb8a7), txIndex 0xf |
| timestamp | 2026-06-18T15:43:25Z |
| from | 0x9bd2…9f70 (the payer ✓) |
| to | 0x4d50500000000000000000000000000000000000 (MPP precompile — ASCII "MPP") |
| nonce | 20 (the payer's latest tx) |
| status | 0x1 = SUCCESS, gasUsed 295037 |
Decoded logs (this one tx carries the whole channel open):
- pathUSD
Transfer— payer → MPP precompile,1.000000pathUSD — the channel deposit (headroom = SDKmaxDeposit: '1'). - MPP channel-OPEN event (from the
0x4d5050…precompile):channelId=0x7c1261fb43253cc772fe045d90e321c7515f85a3b1b74b375c942e0b0ca7cacc- payer =
0x9bd2…9f70 - payee =
0xa726a1cd723409074df9108a2187cfa19899acf8✓
- pathUSD
Transfer— payer → fee collector (0xfeec0000…),0.005902pathUSD (channel-open fee).
| Units paid | 1 response-chunk (unitType: response-chunk) |
| Amount metered | 0.0002 pathUSD (the price per response-chunk) |
| Client tally | 💸 [units paid: 1 | 0.0002 pathUSD] |
| Settlement | signed MPP voucher (off-chain); the unused 0.9998 of the deposit reclaims on close/timeout |
Honest note — do not conflate the two numbers. What moved on-chain is the 1.0 pathUSD deposit (locked headroom) plus the 0.005902 pathUSD open fee. The 0.0002 pathUSD is the actual metered spend, settled off-chain by voucher. This is standard MPP session design, not a discrepancy.
The agent decrypted the enclave's ciphertext locally. Plaintext was seen only by the agent and the attested enclave. First ~300 chars:
**Short-Answer**
1. **Blast radius:** Depends on the scope of permissions the key carries.
* If it's an API key for a third-party service (e.g., Stripe, SendGrid, Slack), the blast
radius is limited to the operations that key permits…
* If it's a cloud-provider key (AWS IAM, GCP Service Account, Azure AD App)…
Why this is genuine model output (not a stub): the full answer is a structured
incident-response runbook (blast-radius analysis, a 10-step rotation order, per-platform
AWS/GCP/Stripe tables) and — decisively — it generated a grep snippet echoing the exact
leaked key from the prompt (sk-proj-1a2b3c4d5e6f7g8h9i0jklmnopqrstuvwx). A stub cannot reproduce
prompt-specific content inside generated code. Consistent with nosana:gpt-oss:20b.
The prompt and the answer are encrypted end-to-end between the agent and the attested enclave (Arcium RescueCipher + X25519). mppRouter is structurally blind: its code has no decryption path, holds no key, and only ever moves opaque ciphertext in both directions.
Code (verified):
- The SDK encrypts before sending — the request body is only
{ encryptedPrompt, model }; the raw prompt is never in it (sdk/src/index.ts:136,147). - The decryption key never leaves the agent —
packageForTEE()serializes only{ciphertext, nonce, publicKey, algorithm, version}and drops the ephemeral private key (@solrouter/sdkencryption.js);decrypt()runs locally (sdk/src/index.ts:166). - The relay never decrypts —
server.ts:129destructures only{ encryptedPrompt, model }(nomessages/content), imports no decrypt code / no@solrouter/sdk, forwards ciphertext to the enclave, and logs byte lengths only (server.ts:130,132). - The answer is ciphertext on the wire too — the enclave returns
encryptedResponse; the relay streams the chunks verbatim; the agent reassembles and decrypts locally.
Live proof (no payment). Encrypting a unique marker E2E_MARKER_9f3c__sk-proj-LEAKTEST to the
live enclave key, the on-wire payload is:
{ "ciphertext": "eyJkYXRhIjpbIjQwI…", "nonce": "BNyHYA+rXu4BCqGSwjEREA==",
"publicKey": "oKAFD8TOTnaf… (agent EPHEMERAL public key — safe to share)",
"algorithm": "Arcium-RescueCipher", "version": "2.0-packed31" }marker present on wire? false ← plaintext never on the wire
private key present on wire? false ← only the EPHEMERAL public key is sent
decrypt with agent key → "E2E_MARKER_9f3c__sk-proj-LEAKTEST" (round-trips)
decrypt with a wrong key → garbage (recovers nothing)
What is plaintext (non-sensitive routing metadata, no content leak): the model id, the
algorithm/version tags, the agent's ephemeral public key + nonce (these must be public for
X25519), and approximate byte lengths in server logs. On-chain payment metadata is financial, not
content.
No plaintext fallback. If the enclave public key can't be fetched, @solrouter/sdk throws
("Refusing to encrypt — would risk sending plaintext") — the old guessable-key fallback was removed
— and the attestation gate fail-closes before encryption, so a bad enclave means zero bytes sent.
- No cooperative-close / settlement tx. Scanning the MPP precompile for
channelId 0x7c1261fb…returns exactly one event — the OPEN — and no close. The production server runs close best-effort (no payee settlement key set), so the channel deposit reclaims on timeout rather than settling on-chain. Vouchers are off-chain, so this is the expected MPP shape, not a failure.
chainId 0xa5bf = 42431 ✓
nonce (latest) 21
native gas balance seeded sentinel (Tempo testnet ~free gas)
pathUSD balanceOf 1,999,997.624119 pathUSD (well-funded)
The explorer (explore.testnet.tempo.xyz) is a custom SPA with no public REST API (its
indexer tidx.tempo.xyz needs auth), so this evidence was reconstructed from the public RPC
https://rpc.moderato.tempo.xyz via eth_getLogs — anyone can re-run it:
RPC=https://rpc.moderato.tempo.xyz
TX=0xaa213739980b114a516ac38588703be1cd72fa4ef96d1e9b98227ce990cb915b
# the transaction + receipt (status 0x1, to = MPP precompile 0x4d5050…)
curl -s $RPC -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_getTransactionReceipt","params":["'$TX'"]}'
# payer pathUSD balance (TIP-20 balanceOf @ 0x20c0…, 6 decimals)
curl -s $RPC -H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"eth_call","params":[{"to":"0x20c0000000000000000000000000000000000000","data":"0x70a082310000000000000000000000009bd2b3c6dc9bdf069333eaec42596e6d119c9f70"},"latest"]}'cd mppRouter
# fund a Tempo testnet key (see README), then:
SERVER_URL=https://mpprouter.onrender.com AGENT_PRIVATE_KEY=0x… npm run agent
# → detect → DCAP verify → encrypt → open MPP channel (on-chain) → pay per chunk → decrypt
# free attestation gate only (no payment):
SERVER_URL=https://mpprouter.onrender.com npm run cli -- verifyScope: Tempo Moderato testnet, pathUSD, OSS model (gpt-oss:20b) in the enclave.
Verification covers DCAP cert-chain + key binding + enclave ed25519 signature; code-measurement
pinning is opt-in (soft-pin by default). The enclave key is attached to the MPP session as a
settlement label, not an enforced gate — see docs/adr/0002. Captured 2026-06-18.
Three real testnet runs, each tripping a different detector category. All passed pre-pay DCAP verification, paid 0.0002 pathUSD per chunk, and decrypted successfully.
| # | Detector | Prompt type | TX hash |
|---|---|---|---|
| 1 | openai-key, high-entropy-token |
Leaked credential → incident response | 0x2044…57d75 |
| 2 | email |
Support ticket with PII → GDPR-safe triage | 0xbd63…e24f |
| 3 | hex-private-key |
Wallet key exposure → security review | 0x23d1…dee2 |
All three: 1 unit · 0.0002 pathUSD · total 0.0006 pathUSD on Tempo Moderato testnet.