The life of a request
ZeroSignal is an open network for paid AI inference. Independent operators run the models. Your client seals each prompt to the one node that will answer it, pays for that request from an on-chain escrow, and gets back a signed receipt it checks against the quoted price. The network's guarantees depend on the order of these steps.
The cast
Operator and node. An operator is an on-chain identity: an owner address that gets paid and a stake that backs it. Under it run one or more nodes, each a single endpoint with its own URL and signing key. Clients route to a node, not to the operator as a whole. See What is an operator.
Relay and target. These are per-request roles. Every node forwards other people's sealed traffic and answers requests of its own, but never both for the same request.
Client and payer. A client is the software that does your side of the protocol: finding operators, reserving a price, opening escrow, sealing the prompt, checking the receipt, and settling. There are two. The chat app runs the protocol in your browser. zs-proxy runs on your machine and serves an ordinary OpenAI API on localhost, so an existing tool can use it without knowing about the protocol. Both share one payer — the Algorand account that funds the escrow — so you can run both. On this page, "you" means whichever client you run.
The shape of the whole thing
Six phases: discover a node, reserve a price, open escrow, send the sealed prompt, collect the receipt, settle on chain.
The price is signed before you pay it. The key your answer will be sealed with is named before any money moves. The money is escrowed before the prompt is sent. The amount charged is signed before either side can settle, and neither side can settle alone.
The operator signs the price ceiling before it sees the prompt.
Discovery
Every node publishes a public, unauthenticated details document over plain HTTP: the models it serves, its prices, its protocol version, and a short-lived public key to seal requests to. The node's long-term key signs that key, which lives about twenty minutes, so a key recovered later can't open earlier requests. See Encryption & keys for rotation.
Your client finds candidates by reading the operator registry on chain. It probes them on a background schedule through a relay — one registered node forwarding the client's HTTP probes to another — so it doesn't hand its address to every operator it merely considers.
Reserve
A reserve names a model, a token budget, and whether the reply will stream. It carries no prompt. Request and response are both sealed, because the request names your payer account and the response carries a transaction that names it again.
One round trip returns three things, each used in a different later phase:
- A signed ticket — the operator's quote for exactly this model, budget and stream flag: the rates, a ceiling on what it may charge, an expiry, and a commitment (a hash) to the key it will seal your answer with.
- The response key, wrapped to you — encrypted so only your client can unwrap it. The ticket already commits to its hash, so an answer sealed with any other key is provable misbehaviour.
- A pre-signed escrow call — the operator signs the transaction that opens escrow but doesn't submit it. You complete the group and broadcast it, so the operator can pay the network fees for a payment only you can make.
A signed ceiling can still be set too high, so before opening escrow, your client recomputes the ceiling from the ticket's signed rates and checks it against the card the operator advertised. If they don't reconcile, the client refuses the ticket and tries the next candidate. See The payment flow for how the ceiling is sized and Pricing for what you actually pay.
Escrow
Opening escrow is one atomic group of two transactions: both commit or neither does. The first is your USDC transfer of exactly the ticket's ceiling. The second is the operator's pre-signed call, which writes a record of the ticket on chain and marks it open.
No ALGO moves on the ordinary path. The on-chain reserve that every stored record requires comes from a deposit you make once and reuse, and the operator pays the network fees on this call and on the settlement later.
The second transaction's id becomes the request's id. It ties the prompt you are about to send to the money you just escrowed, so neither can later be swapped for another.
This step is public and names your account, as settlement does later — see Where it is qualified.
Serve
The request that leaves your device is one JSON object with one field: ciphertext. A relay sees only the routing envelope around it.
POST /v1/zs/relay
X-Zs-Relay-Target: <operator id> # on-chain ids — the relay
X-Zs-Relay-Target-Node: <node id> # resolves them itself,
X-Zs-Relay-Path: /v1/chat/completions # never a URL
X-Zs-Relay-Method: POST
Content-Type: application/vnd.zs+json
{ "ciphertext": "..." } # forwarded byte-for-byte
Everything the target needs to admit the request is inside the seal:
sealed envelope — opens only with the target's short-lived key
├─ reply_to_public_key
├─ algorand_tx_id
├─ ticket_id
├─ admission_tag
└─ body — the OpenAI request, unchanged
bound into the encryption, but never transmitted:
algorand_tx_id · ticket_id · frame_index
The values on the last line must match at both ends for the ciphertext to open, but they are never sent, because both ends already hold them. So a ciphertext sealed for one request can't be spliced onto another, and a stream's frames can't be reordered.
The escrow transaction is public, so the ticket's id is public too, and anyone watching the ledger could try to spend your ticket before you do. The admission tag prevents that. It is a keyed hash over the ticket, the transaction and the body, computed with the response key, which only your client and the target node hold. The target checks it before it consumes anything:
- Look up the ticket, without consuming it.
- Verify the admission tag.
- Check the reply-to key against the one named at reserve.
— the ticket is consumed below this line —
- Consume the ticket.
- Verify the escrow transaction on chain.
- Check the model matches the one the ticket was priced for.
- Apply the node's own request defaults, then forward upstream.
A failure above the line consumes nothing, so a forged tag costs neither the attacker nor you anything. Below it the ticket is spent and the node has done real work, so a failure there produces a receipt for zero rather than silence.
Here the target decrypts the prompt, and the protocol stops covering it. If the node serves from its own weights, the plaintext goes no further. If it serves through a hosted API, the plaintext continues to that provider. On a confidential node, decryption happens inside attested hardware the operator can't read.
The answer, and the receipt
Your client refuses any unsealed response to an admitted request.
A streamed answer arrives as sealed frames, followed by two more: a sealed transaction group ready for settlement, and a sealed receipt. Then comes the ordinary [DONE] line that OpenAI-compatible tools stop on.
index arrives from the node what your client passes on
----- ------------------------------- --------------------------
0 event: zs --> data: {"choices":[...]}
1 event: zs --> data: {"choices":[...]}
- : keepalive (dropped)
2 event: zs --> data: {"choices":[...]}
- event: zs-settle-group consumed — never passed on
3 event: zs-receipt consumed — never passed on
- data: [DONE] --> data: [DONE]
- = not sealed under a frame index, so it doesn't advance the counter
The receipt records what actually happened — real input and output counts, the amount charged, the timing — plus a hash of the answer, signed by the same key that signed the ticket. Your client checks that the signature holds, that the amount is within the ceiling you escrowed, and that the hash matches the answer it decrypted.
The encryption doesn't catch a stream cut short. Dropping the trailing frames leaves a well-formed, correctly sealed, incomplete answer; only the completion signals in the response show that it ended early.
Settle
The operator claims an amount, you acknowledge it, and the escrow finalises only when both halves agree. Normally both halves go in one atomic group, submitted as soon as your client has verified the receipt. Three payments leave escrow at once: the operator's charge, the protocol fee, and the remainder back to you.
Neither side can finalise alone. Acknowledging is how you cap what you pay; if you don't acknowledge, the operator's claim stands at the deadline.
The deadline is the ticket's expiry plus a grace window. Before it, either side can freeze the ticket by disagreeing. After it (the dashed paths), the ticket resolves without further consent, and anyone can submit the transaction, so neither side depends on the other staying online. Exactly one of the two outcomes is ever reachable, and which one depends only on whether the operator's claim was already standing when the deadline passed.
Freezing stops the money and preserves both sides' signed claims as non-repudiable evidence, but there is no on-chain arbiter to weigh them. Until something off chain does, the escrowed USDC and the deposit slot behind the record stay locked. The payment flow covers both disagreement paths from the operator's side.
What each party knows
The relay sees your address but only ciphertext. The target node decrypts the prompt but sees only the relay's address. See Encrypted end to end.
Every node plays both roles, so over time one operator sees many addresses as a relay and many requests as a target. It never sees both halves of the same request: the relay and the target are always different operators, enforced on their owner keys, so no single operator joins the two. Joining them takes two owners colluding, or one party registering operators under several owner keys. If no relay qualifies, the request fails rather than going direct — see Relays.
Where it is qualified
You are pseudonymous to the target, not anonymous. The target sees no name, account, or IP address, but it reads your payer address from the reserve and checks it against the chain. That address stays the same across requests unless you rotate it, so a target can link your requests through it, and the fact that you paid that operator at that time is public. The payer address is what makes escrow-backed refunds and a disputable receipt possible.
The ledger is a second path between the two halves — the dashed arrows in the diagram. Opening escrow is public, so a relay that logs which address it forwarded for, to which target, and when, can scan the chain for a matching open near that moment. If the target is lightly loaded and the timing is tight, that recovers the payer. Because every leg is sealed, the relay can't read the payer off the wire; it can only correlate its logs with the public ledger statistically. This does not guarantee unlinkability.
Metadata is visible. The path, the method, the timing and the size of the ciphertext are all on the wire, and nothing hides that the protocol is in use.
Collusion defeats it. Joining the two views requires the relay and target to cooperate, or one party to be both. Owner-key diversity stops a single owner from being both, so the remaining risk is a Sybil: one operator registered many times under distinct keys. Multi-hop relaying is the planned answer and isn't available yet.
The one thing still trusted
Everything above concerns who learns that you asked. Who can read what you asked depends on the node — see What the operator can see. On a standard node, its operator could read the prompt while the model runs, and so could any hosted provider it forwards to. On a confidential node, only the attested hardware can decrypt it, because the key your prompt is sealed to is generated inside that hardware.
The hardware measures the software it runs. Attaching a debugger, adding prompt logging, or swapping in a modified build changes that measurement, and a node whose measurement no longer matches the published build loses its attested status, so requests that require attested hardware stop going to it.
Confidential nodes are live but not the default, and only some operators run them. Today they attest the Intel TDX CPU only; GPU confidential computing isn't wired yet. Your client checks each node's hardware evidence against Intel's certificate chain and the published build. Turn on Only use attested nodes under Settings → Model routing to send only to nodes that pass, and see Verifying an attestation to check a node yourself.
Want More?
- The payment flow — reserve through settle from the operator's side, including how a node sizes its ceiling and drives settlement.
- Encryption & keys — the key hierarchy, rotation, and forward secrecy.
- Relays — how a relay is chosen, and the rules that keep relay and target apart.
- Privacy & security — the same privacy split without the protocol detail.
- Verifying an attestation — what attested hardware proves, and how to check it.