How it works
Five steps, and no one in the middle.
Requests, bids, acceptances, payments and closes are signed frames inside encrypted Zcash memos. The job, its logs, heartbeats and results are too big for a memo, so they move over a relay that only ever holds ciphertext.
Buyer
silicr CLI · terminal
- Shielded wallet
- Ranks bids, picks one
- Pays per interval
ZCASH · SHIELDED (IRONWOOD)
- ① RFQ · sealed to each provider asked
- ② BID · signed, to a one-time address
- ④ ACCEPT, then one PAY per interval
- CLOSE · from either side
Provider
silicr provide
- Shielded wallet
- Bids by policy
- Docker sandbox, watchdog
③ Silicr relay: ciphertext only, no keys
Off the chain, to one side: buyer and provider swap the sealed job, logs, heartbeats and results here.
Step 1: Sealed RFQ
Your client picks matching providers from the public, signed directory and sends one shielded transaction. Each output carries your request in an encrypted memo only its recipient can read: GPU, count, duration, max price, a one-time reply address, a signing key made for this request and a channel key. Outputs are padded to 4, 8 or 16 with dummies to fresh addresses of your own, so the chain can't tell how many providers you asked within that bucket.
Who can read it
Only the providers you ask
Step 2: Sealed bids
Each provider answers with a BID memo signed by its directory key and sent to your one-time address. Your client checks every signature and drops bids that are late, above your max, or from providers you didn't ask. Each bid names a fresh payment address and its own channel key.
Who can read it
Only you
Step 3: Encrypted job channel
The channel keys in your RFQ and the winning bid meet in X25519; HKDF-SHA256 turns the shared secret into one XChaCha20-Poly1305 key. The job, logs, heartbeats and results are sealed with it and pass through a relay that stores only ciphertext, under mailbox ids derived from the same secret. No memo ever names a mailbox.
Who can read it
You and the winning provider
Step 4: Metered settlement
The ACCEPT transaction pays interval 0. After that your client pays each interval in shielded ZEC by its own clock, shortly before it begins, with random jitter. Heartbeats only confirm the job is alive, so a provider can't speed payments up by claiming progress. The provider runs only paid intervals: neither side can lose much more than one interval's worth.
Who can read it
You and the provider
Step 5: Bonded providers
Roadmap · not builtPlanned: providers lock a bond in a FROST 2-of-3 shielded address (provider, buyer pool, arbiter) that can be slashed on signed evidence of fraud. Not built yet: it is a stretch goal, and the arbiter would be a trusted party.
Who can read it
Bond holders
No matching engine
There is no order book on a server and no operator who sees every order. Bids land at your one-time address and your own client ranks them: by price per hour, then bond size (bonds are on the roadmap), then arrival. You accept one yourself, or set an auto-accept rule such as “the cheapest bid after waiting two blocks”.
Leak Inspector
Don't take the diagram's word for it.
Every screen in the terminal has a Leak Inspector toggle. Press L and the same moment appears from four viewpoints side by side: the public chain, the Silicr relay, the provider and the buyer.
Each column lists what that party actually received, recorded from real data rather than drawn: decoded memos, relay requests and public transaction views. What a party can read is marked; ciphertext stays ciphertext.
Leak Inspector · the moment you accept a bid
press L in the terminalPublic chain
- A shielded transaction
- v6 · 2 Ironwood actions
- Fee
- 10,000 zats
- Sender, recipient, amount, memo
- hidden
Silicr relay
- Stored opaque blob #1
- 48,212 B · mailbox 3f9ca1e2…
- First 24 bytes
- 9b1e04c7d2…
- Client IP
- 203.0.113.24
Provider
- Bid won
- ACCEPT for 144 intervals
- Price
- 0.06 ZEC per 300 s
- Buyer identity
- not present in any message
Buyer
- Job sealed and uploaded
- command: python train.py
- Paid now
- 0.06 ZEC (interval 0)
- Relay mailbox
- derived from the channel secret
Who learns what
This is the table in THREAT_MODEL.md, the one the inspector fills in live. It also covers two parties the live view can't show: the light-wallet server your wallet syncs from, and the providers that bid and lost. Knows means the party can read it in the clear. The same file lists what Silicr does not protect: the winning provider sees the workload, and without Tor the relay and light-wallet server see your IP and timing.
| What about your job | Public chainanyone watching | Light-wallet serverlightwalletd | Silicr relayits operator | Providers askedlosing bidders | Winning providerruns the job | Buyeryou |
|---|---|---|---|---|---|---|
| Who the buyer is: identity, wallet | Does not know | Does not know | Does not know | Does not know | Does not know | Knows |
| The buyer's IP address | Does not know | Knows | Knows | Does not know | Does not knowtalks only to the relay | Knows |
| Which transactions that IP broadcasts, and when | Does not knowsees them, not who sent them | Knows | Does not know | Does not know | Does not know | Knows |
| Which transactions belong to one job | Does not knownot linkable on-chain | Knowsyour IP and the payment train | Does not know | Does not know | Knowsits own payments | Knows |
| Roughly how long the job ran | Does not know | Knowsfrom the payment count | Knowsfrom blob timing | Does not know | Knows | Knows |
| Blob sizes, upload and download times | Does not know | Does not know | Knows | Does not know | Knowsits own | Knowsits own |
| The request: GPU, count, duration, max price | Does not know | Does not know | Does not know | Knows | Knows | Knows |
| Which bid won, and at what price | Does not know | Does not know | Does not know | Does not know | Knows | Knows |
| Amounts paid | Does not knowonly fees are public | Does not know | Does not know | Does not know | Knows | Knows |
| The workload: job bundle and its outputs | Does not know | Does not know | Does not knowciphertext only | Does not know | Knows | Knows |
| When shielded transactions happen, their action counts and fees | Public to everyone. Payments look like ordinary 2-action shielded payments; a padded RFQ (5–8, 9–12 or 17–20 actions) is a less common shape. Senders, recipients, amounts and memos stay hidden. | |||||
Who the buyer is: identity, wallet
Known to
- Buyer
Not known to: Public chain, Light-wallet server, Silicr relay, Providers asked, Winning provider
The buyer's IP address
Known to
- Light-wallet server
- Silicr relay
- Buyer
Not known to: Public chain, Providers asked, Winning provider (talks only to the relay)
Which transactions that IP broadcasts, and when
Known to
- Light-wallet server
- Buyer
Not known to: Public chain (sees them, not who sent them), Silicr relay, Providers asked, Winning provider
Which transactions belong to one job
Known to
- Light-wallet server(your IP and the payment train)
- Winning provider(its own payments)
- Buyer
Not known to: Public chain (not linkable on-chain), Silicr relay, Providers asked
Roughly how long the job ran
Known to
- Light-wallet server(from the payment count)
- Silicr relay(from blob timing)
- Winning provider
- Buyer
Not known to: Public chain, Providers asked
Blob sizes, upload and download times
Known to
- Silicr relay
- Winning provider(its own)
- Buyer(its own)
Not known to: Public chain, Light-wallet server, Providers asked
The request: GPU, count, duration, max price
Known to
- Providers asked
- Winning provider
- Buyer
Not known to: Public chain, Light-wallet server, Silicr relay
Which bid won, and at what price
Known to
- Winning provider
- Buyer
Not known to: Public chain, Light-wallet server, Silicr relay, Providers asked
Amounts paid
Known to
- Winning provider
- Buyer
Not known to: Public chain (only fees are public), Light-wallet server, Silicr relay, Providers asked
The workload: job bundle and its outputs
Known to
- Winning provider
- Buyer
Not known to: Public chain, Light-wallet server, Silicr relay (ciphertext only), Providers asked
When shielded transactions happen, their action counts and fees
Public to everyone. Payments look like ordinary 2-action shielded payments; a padded RFQ (5–8, 9–12 or 17–20 actions) is a less common shape. Senders, recipients, amounts and memos stay hidden.
- Light-wallet server (lightwalletd, the server your wallet syncs from): also learns each wallet's birthday height and any transaction the wallet fetches by txid. It can't tell which notes belong to the wallet; blocks are scanned locally.
- Directory host: the public provider entries, and the IP of whoever publishes or reads them; nothing about buyers or trades. Entries are signature-checked by the relay on write and by every client on read, so the host can drop an entry but can't forge or replace one. In this build the relay serves the directory.
- Winning provider: can't link the job to the buyer's other jobs, because each RFQ uses a fresh key and address.
- Buyer: can't learn providers' other jobs or earnings.
- Silicr arbiter (roadmap, FROST bonds): would see bond addresses and the evidence submitted in disputes, and nothing about undisputed trades.
Settlement
Pay as you go, in shielded ZEC.
Zcash has no escrow contract to lean on, so Silicr doesn't hold money at all. It caps what either side can lose instead: the buyer pays one interval at a time, and the provider runs only what has been paid for.
- Interval 0: running, paid by the ACCEPT; PAY 1 goes out shortly before it ends.running
- Interval 1: running, PAY 2 goes out shortly before it ends.running
- Interval 2: running, PAY 3 goes out shortly before it ends.running
- Interval 3: running, the buyer stops paying: no PAY 4.running
- Grace: paused, interval 4 unpaid: the job is paused for up to 3 min.paused
- After: stopped, stopped, signed CLOSE (unpaid), no results.stopped
- running
Interval 0
paid by the ACCEPT; PAY 1 goes out shortly before it ends
- running
Interval 1
PAY 2 goes out shortly before it ends
- running
Interval 2
PAY 3 goes out shortly before it ends
- running
Interval 3
the buyer stops paying: no PAY 4
- paused
Grace
interval 4 unpaid: the job is paused for up to 3 min
- stopped
After
stopped, signed CLOSE (unpaid), no results
- Interval 0 is paid by the ACCEPT transaction, before any work starts. The job clock starts when the provider's first signed heartbeat arrives.
- Every later interval is paid by the buyer's own clock, shortly before it begins (1 min ahead by default on testnet), with random jitter so payments have no fixed rhythm.
- Heartbeats only confirm the job is alive. A missing or stale heartbeat holds a payment back, but no heartbeat can bring one forward: a provider can't speed up payments by claiming progress.
- The provider never computes an unpaid interval. At an unpaid boundary it pauses the job; if the payment hasn't arrived by the end of the grace period, it stops, sends a signed CLOSE (unpaid) and uploads no results.
- The buyer has a watchdog too: after 90 s without a signed heartbeat it stops paying and sends CLOSE (no heartbeat).
Defaults on testnet
- Interval
- 5 min, quoted in each bid
- Each payment goes out
- about 1 min early, with random jitter
- Heartbeat
- every 15 s, signed and sealed
- Grace before stopping
- 3 min
- Buyer gives up after
- 90 s without a heartbeat
Providers set their own interval and grace. Mainnet uses a longer interval and lead, so fewer payments per hour; silicr status shows the live values.
Worked example
4 × A100 for 12 hours, max 0.8 ZEC/h. The winning bid is 0.72 ZEC/h, quoted as 0.06 ZEC per 5-minute interval.
- Intervals
- 12 h ÷ 5 min = 144
- Payment transactions
- 144: 1 ACCEPT + 143 PAY
- Paid to the provider
- 144 × 0.06 ZEC = 8.64 ZEC
- Network fees on payments
- 144 × 0.0001 ZEC = 0.0144 ZEC0.17% of the job
- All fees, with the RFQ and your CLOSE
- ≈ 0.01495 ZECRFQ to 6 providers: 8 padded outputs + change = 9 actions, 45,000 zats. CLOSE: 10,000 zats.
- Most you can lose
- ≈ 0.072 ZECThe rest of the interval in progress plus the one prepaid interval: about 1.2 intervals, or 6 minutes' worth (0.8% of the job), even against a provider that lies about its progress.
- Most the provider can lose
- no unpaid computeIt never computes an unpaid interval: it pauses at an unpaid boundary and stops after the grace period. It does act on payments while they are still in the mempool, since each is small.
Fees assume each payment spends one note and returns one change note: 2 logical actions, the ZIP 317 minimum. Spending more notes, or splitting change into extra notes, adds 5,000 zats per extra action.
Fees (ZIP 317)
fee = 5,000 zats × max(2, logical actions)
- A PAY is one output plus change: 2 actions, 10,000 zats, or 0.0001 ZEC.
- Each padding output in an RFQ adds 5,000 zats. That is the price of hiding how many providers you asked.
Why no smart contract is needed
- Every payment is a plain shielded transaction carrying a signed PAY memo. Nothing is locked or pooled.
- Nobody holds custody: not Silicr, not a contract, not the relay.
- Instead of escrow, the risk is capped by the interval length. Shorter intervals mean less at stake and more fees.
- An escrow on a transparent chain would publish the deal anyway.
Quick start
Try it on your machine.
You need Node 22 or newer, pnpm 9 and the Rust wallet in zec-sidecar/. The demo runs the relay, a buyer and a provider locally on Zcash testnet, with testnet coins. The real thing runs on Zcash mainnet with small balances.
# from the repo rootpnpm install# relay, buyer and provider on Zcash testnet; runs the tiny-train dealpnpm silicr demo# in a second shell: the terminal, on http://127.0.0.1:7703pnpm --filter @silicr/web dev.silicr/wallets/test-buyer and .silicr/wallets/test-provider (create and fund them with silicr init --network test and a faucet). --network main is refused for the demo: real money. Open the terminal and press L to watch what each party sees.# keys + a shielded mainnet wallet; prints its u1… addresspnpm silicr init --role buyer --network main --data .silicr/wallets/main-buyer# in its own shell; syncs mainnetpnpm silicr daemon --data .silicr/wallets/main-buyer# sealed RFQ, bids, metered paypnpm silicr run --gpu a100 --count 1 --hours 1 --max 0.05 --auto ./job -- python3 train.pyu1… address init prints. It is spendable after 10 confirmations, about 12 to 13 minutes. On mainnet the daemon refuses to start without spend caps (per request and in total) in its config, and the terminal shows the amount and the cap before every send. Testnet: use --network test and a faucet. See docs/MAINNET.md.# keys + a shielded mainnet walletpnpm silicr init --role provider --network main --data .silicr/wallets/main-provider# in its own shellpnpm silicr daemon --data .silicr/wallets/main-provider# publish a signed listing and bid by policypnpm silicr provide --gpu a100 --count 4 --vram 80 --min 0.6cargo build --release in zec-sidecar/) and a relay both can reach (pnpm silicr relay, default port 7700); the provider needs a little ZEC for bid fees. Install Docker for the sandboxed runner, plus the NVIDIA container toolkit to pass GPUs through. Without Docker it falls back to process mode, which is not a sandbox. See docs/ZCASH_SETUP.md.FAQ
Straight answers.
The questions people ask first, including the uncomfortable ones.
Is it on mainnet?
It can be. Silicr runs on Zcash testnet or mainnet through its own light wallet, and the demo runs on testnet. Mainnet is opt-in: an explicit --network main, spend caps in the daemon config (per request and in total; the daemon refuses to start without them) and small balances.
This is unaudited hackathon software. Keep only what you are willing to lose in its wallets, back up the seeds under .silicr/ or treat them as throwaway, and read docs/MAINNET.md first.
Can the provider see my model and data?
Yes. The winning provider decrypts and runs your job, so it sees your code, your data and the outputs. Hiding the workload from the machine that runs it needs TEE execution with attestation, which is future work. Treat a provider as able to read everything you send it.
What it doesn't learn is who you are: no message carries your identity, your wallet or your IP address.
What stops a provider from taking payment and not computing?
Nothing prevents it outright; Silicr limits what it costs you. Your client pays each interval by its own clock, shortly before it begins, and stops as soon as signed heartbeats stop. Claiming progress can't speed payments up. In the 12-hour example on this page, a provider that walks away costs you at most about 0.072 ZEC, or 6 minutes' worth.
Heartbeats carry a step count and a checkpoint hash. They show the job is progressing, not that it ran on the claimed GPU or produced honest outputs. Provider bonds with slashing (FROST 2-of-3) are on the roadmap.
Does Silicr hold my funds?
No. There is no escrow, no custodian and no Silicr account. You pay the provider directly from your own wallet with ordinary shielded transactions, one interval at a time.
What does the relay know?
Blob sizes, upload and download times, and the IP addresses of the parties connecting. It stores only ciphertext, under mailbox ids derived from the buyer and provider's shared secret, so it can't tell which request a blob belongs to, what it contains, the price or who anyone is.
In this build the relay also serves the public provider directory. It is a small Node server that holds no keys, so you can run your own.
And the light-wallet server?
Your wallet syncs from the lightwalletd server you point it at (testnet.zec.rocks by default on testnet). It sees your IP address, every transaction you broadcast and when, and your wallet's birthday height. That is enough to link one job's transactions, a padded RFQ followed by a run of 2-action payments, and to estimate how long the job ran: random jitter blurs the cadence, but not the count. It can't read memos, amounts or recipients, and blocks are scanned on your machine. Tor or SOCKS support, batched payments, or running your own Zebra + Zaino/lightwalletd would reduce this; all are future work.
Who sees my request?
Only the providers you ask: your client picks up to 16 matching ones from the directory, or you name them. They learn the GPU type, count, duration and your max price, but not who you are, and the ones that lose never learn which bid won. The chain sees a padded transaction that doesn't show how many providers you asked, within its bucket.
Why Zcash?
A shielded Zcash transaction hides sender, recipient and amount, and every output carries a 512-byte encrypted memo. That gives Silicr a private payment rail and a private message channel in one, with no separate messaging network. Unified addresses make a fresh, unlinkable address per request cheap, and metered payments replace the escrow contract Zcash doesn't have.
Launch
See what each party learns, for yourself.
Open the terminal, send a request and press L. Every column shows what that party actually received.
Launch app opens app.silicr.xyz: the terminal runs on your machine, next to your Silicr daemons. The threat model is THREAT_MODEL.md in the repository.