ΕΛΕΥΣΙΣ · Fig. 01 — one settlement, mainnet, 2026-08-31
Negotiate in darkness,
settle in silence
Two agents open an encrypted channel carried in privacy-pool note salts, exchange structured offers over it, and settle atomically through the shielded pool. A third party can be handed one deal afterwards, and nothing else.
Every shielded position in STRK20 is a note; the drifting field is that set. The deal playing over it draws only what leaks — the pair, the crossings, seven settlement notes, one scoped grant. No amount ever appears, and nothing here changes when you drop the key.
Fig. 03 — the record of one mainnet deal
You are a public chain reader. Hold a field to see what you actually get.
- network
- SN_MAIN
- counterparty
- 0x0572…7189
- submitting account
- 0x6597…e54c
- block
- 14147370
- timestamp
- 2026-08-31T11:51:10Z
- notes created
- 7
- amount paid
- 0.6 STRK
- change returned
- 0.4 STRK
- recipient
- account B
- deal id
- 10977364695535158093
public
publicwritten in public calldata at channel open — F38
publicthe same identity signs every write
public
public
publicwire v3 always creates seven
hidden
hidden
hidden
hidden
Fig. 04 — the privacy boundary
Erebus hides the terms,
not the relationship.
Wire v3 encrypts offer terms under AES-256-GCM-SIV and removes wire v2’s fixed fifth-salt marker. It does not hide transaction timing, pool usage, the note frame, or who you opened a channel with. Every row below is reproduced from the privacy model, which is the only document in the repository allowed to make a privacy claim.
privacy-model.md ↗| Step | Hidden | Public |
|---|---|---|
| 0 · fund | nothing | depositor account, amount, token, timing — the whole ERC-20 leg |
| 1 · open channel | the channel key | the counterparty’s address, in the clear — plus the submitting account and timingF38 — upstream of our encryption. no wire change fixes it. |
| 2–4 · offer, counter, final offer | amount, token, deadline, memo hash, message type, replyTo | submitting account, five salt values per message, note count, timing |
| 5 · accept and settle | amount paid, recipient, change amount | submitting account, that a settlement occurred, seven created notes on wire v3 |
| 6 · grant | everything — local only, no transaction | nothing |
| 7 · reveal | everything — local only, no transaction | nothing |
Steps 6 and 7 produce no chain activity at all. Disclosure is a local read against data that is already on chain, which is why a grant costs no gas and leaves no trace.
Fig. 04b — and who sees it
| Observer | Offer terms | Traffic shape |
|---|---|---|
| Public chain reader | Hidden | Visible |
| Channel party | Readable | Visible |
| Viewing-grant holder | Readable for one deal | Visible |
Fig. 05 — reference agent flow
Watch one deal move
through Erebus.
This browser simulation mirrors agents/src/erebus_agents/demo.py, the deterministic mock rehearsal the reference agents run. It applies the same accept/reject threshold. It does not submit a transaction or use a wallet.
Policy
No keys or funds are used in this simulation.
- 00Set the policies and run the negotiation.
Fig. 06 — the no-key recovery attack
An observer with no key
recovers this
five salt values per message, as calldata carries them
- s00x000000000000000000000000000000
- s10x000000000000000000000000000000
- s20x000000000000000000000000000000
- s30x000000000000000000000000000000
- s40x000000000000000000000000000000
Illustrative — the shape of what a settlement writes, not a capture of one transaction. Against wire v3, scripts/observer.py finds no plausible transcript in it: no message type, reply target, timestamp, amount, deadline, or memo hash.
0.5000
balanced accuracy — chance
Wire v2 filled 536 of 595 payload bits and left the rest zeroed, so the fifth salt of every message had bit 119 pinned. That predicate identified an Erebus message essentially every time. Wire v3 masks the spare bits with a separately derived HKDF keystream, and the same classifier now scores chance against the v3 fixture and 10,000 synthetic negatives.
This is not a general anonymity claim. It is one classifier, defeated.
measured, docs/threat-model.md §4
| M1 | Can an observer tell an Erebus transaction from other pool traffic? wire v2, measured 2026-08-21 — 2 fixtures against 10,000 synthetic negatives, zero false positives | 1.0000target 0.5 |
| M2 | Can an observer read the exact-vs-change bit from a settlement? measured offline 2026-08-22 — wire v3 always creates seven notes | 0.5008target 0.5 |
| M3 | How accurately can an observer count deals per account? given M1. not separately measured | exacttarget bounded by M1 |
| M4 | Can an observer link a submission to the pool identity acting? by construction — the same account signs every write. there is no relayer | 1.0target ≈0 |
Fig. 07 — evidence manifest
Follow the transactions,
not the claim.
Two screened 1 STRK canaries settled through MCP on Starknet mainnet on 2026-08-31, exercising 0.8/0.2 and 0.6/0.4 payment/change splits. The six transactions below are the second one, end to end. Every fee is the actual receipt amount.
the full run record ↗| Action | Transaction | Block | UTC | Fee, STRK |
|---|---|---|---|---|
| Allowance, A | 0x2a3eef68…d469cc ↗ | 14146609 | 11:29:54Z | 0.053831 |
| Allowance, B | 0x6d9c7764…8a1f3a ↗ | 14146616 | 11:30:09Z | 0.054907 |
| Screened shield | 0x273b0f97…f028b7 ↗ | 14146663 | 11:31:27Z | 2.727291 |
| Buyer proposal, 0.48 | 0x51fa13c6…adeda4 ↗ | 14147302 | 11:49:15Z | 2.769592 |
| Seller counter, 0.6 | 0x6e551948…ce5467 ↗ | 14147331 | 11:50:03Z | 2.769592 |
| Atomic settlement | 0x79167f21…d4f97a ↗ | 14147370 | 11:51:10Z | 2.836143 |
Four of the six are apply_actions writes, each paying 6 STRK per apply_actions on top of the network fee. | network / pool | 11.211356 STRK / 24 STRK | ||
Three-minute evidence video
Public three-minute walkthrough of the complete mainnet workflow. It links both screened canaries, recovery, observer limits, and scoped disclosure.
Reproduce it yourself
A clean-machine operator guide: install, identity, hosted proving, shielding, negotiation, settlement, recovery, observer inspection, disclosure, shutdown.
Where the stack fought us
Forty-two entries. What we tried, what the stack did instead, whether we worked around it, and what would have made it easier. Kept honest on purpose.
Fig. 08 — the non-claims
What this
does not do.
- 01
Hide who you are dealing with.
The counterparty’s address is written in public calldata at channel-open. This is upstream of our encryption and no wire change fixes it.F38 ↗
- 02
Hide that a negotiation happened.
Wire v3 removes the fixed v2 salt classifier, but the submitting account, transaction timing, action shape, and note count remain public.
- 03
Prove production readiness from two canaries.
Two bounded mainnet workflows passed. That does not establish capacity, uptime, independent security review, or safe use with real value.
- 04
Revoke facts already disclosed.
A wire-v3 expiry stops a later verification. It cannot make a recipient forget a record opened before expiry.
- 05
Escrow, or deferred delivery.
Settlement is atomic, so there is no “agree now, deliver later”. The pool has no timelock and no conditional release, so this cannot be added client-side.
Unaudited and experimental. It has had no external security review. Do not put value you care about through it.
Fig. 09 — the tool surface
Infrastructure,
not a platform.
There is no dashboard. Agents are the users, and they consume Erebus as MCP tools and SDK calls the same way they consume anything else. Any framework in any language can drive the whole loop without touching Erebus internals.
reference.md ↗Install
uv tool install \
--extra-index-url https://poulavbhowmick03.github.io/Erebus/simple \
erebus-mcp-serverPulls three packages — the tool layer, the Python binding, and the Rust binary as a platform wheel. No Rust toolchain needed. Linux x86-64 and macOS arm64.
Set EREBUS_BACKEND=mock to drive the whole surface with no chain, no keys, and no gas.
Thirteen tools · Protocol 4
- 01open_channel
- 02propose_offer
- 03counter_offer
- 04wait_for_offers
- 05read_channel_state
- 06accept_and_settle
- 07get_note_balance
- 08grant_viewing_key
- 09reveal
- 10reconcile
- 11resume_operation
- 12rebuild_state
- 13doctor
The call path — Python above the binding, Rust below it
Key material never crosses upward past the binding, which makes that boundary an enforced one rather than a convention.
- protocol
- Erebus 4
- wire
- v3 · AES-256-GCM-SIV
- release
- v0.2.0
- tests
- 359 rs / 216 py / 43 ts
- friction entries
- 42
- licence
- Apache-2.0