From DESIGN.md "Threat model".
| Observer | Learns | Doesn't learn |
|---|---|---|
| Chain observer | The escrow address exists and makes batched shielded transactions | Who bet, which side, how much, who won |
| Any single relayer | "A 5-unit deposit arrived for account X"; "someone withdrew A to address Y" | Orders, sides, prices, positions, balances, whose withdrawal it was; any account's tag, so it can't trade, withdraw or read a balance as anyone |
| 2 colluding relayers | Same as one for orders; can move escrow funds (FROST 2-of-3) | Orders (polytune needs all 3 to collude); tags (the third share is missing) |
| All 3 colluding | Everything | — |
| Counterparty | That a fill happened at the midpoint | Who you are, your size beyond the fill |
| Network observer | That someone talks to a zPredict relayer | Contents (TLS); your IP if you use Tor |
| Cloudflare (as deployed, until #88) | Everything the three relayers receive, in clear, plus client IPs: in effect all three colluding | — once the relayer hostnames are DNS-only |
| Demo VM operator | Everything: all three relayers and the DKG ran on one machine | — with three independent operators |
Stated up front: funds are 2-of-3, order privacy is 3-of-3. The demo concentrates all three relayers on one VM behind Cloudflare.
MPC security
polytune runs WRK17 authenticated garbling with distributed preprocessing (no trusted dealer). An honest relayer's shares stay private even if the other two collude and cheat; a cheater can only make the run abort, which is retried next window. Peer links are Noise KK with pinned static keys.
Accounts: a credential no relayer sees
A relayer knows each account's public id and its own share of the tag, never the tag (#85). Orders and withdrawals carry the tag only as fresh XOR shares, and the circuit uses a row only for a request whose shares rebuild that row's key. So a relayer posing as a browser with what it knows gets a dummy order or a refused withdrawal: test a_relayer_cannot_act_as_an_account in differential.rs, and the forged withdrawals in relayer-e2e.sh. Balance reads are gated the same way: relayer i serves a balance share only to a caller holding sᵢ.
Key burn. A dishonest relayer could try to choose the other relayers' shares for an id it learned, by sending a judge claim or a deposit memo of its own for that id first. Honest shares are deterministic in the tag, so the victim's later deposit shows a different share for a known id, and that relayer then burns the key (fresh random share every batch). The row is lost, never handed over: the same denial of service a relayer can always cause by aborting the MPC.
Signers check what they sign
Before a FROST signature on a payout, each signer recomputes the sighash from the PCZT and checks every output against the agreed payout vector (verify_then_sign). A coordinator that changes one output by one zatoshi gets a refusal. See Settlement & FROST.
Not quantum-recoverable (ZIP 2005)
The escrow's spend authority ak is the FROST group key, so its viewing key cannot be derived from a seed; nk and rivk are random, generated once at the DKG. Escrow notes therefore don't use the ZIP 2005 derivation that would let a key holder recover them after a quantum break of discrete log. Acceptable for a hackathon escrow that holds collateral for days; a production escrow needs a FROST-compatible ZIP 2005 derivation, or a sweep to a recoverable key first.