Privacy pools for whales: design notes from the moby-privacy crate
Why Moby Market chose on-chain zero-knowledge proofs over MPC for institutional privacy, and how Pedersen commitments, stealth addresses, and selective disclosure stack into something a compliance officer can sign off on.
Privacy is one of those words that lets everyone agree on a goal and disagree on what counts as success. For an institutional trading desk, “private” means three concrete things: an outside observer cannot read the amount of a trade, cannot link a trade to an identity that maps to other trades, and cannot reconstruct a strategy by watching the chain. Anything weaker fails the use case.
This post walks through how the moby-privacy crate satisfies each of those requirements, why we picked zero-knowledge proofs over multi-party computation, and how the same primitives carry a compliance story strong enough to ship into regulated environments.
What we mean by “private”
The threat model is explicit. The adversary controls a full node, a real-time mempool subscription, and a graph database of every public address. The adversary is computationally bounded but well-funded — call it a sophisticated trading firm or a chain-analytics vendor. The adversary’s goal is to learn either the amount of a trade, the identity of the parties, or the timing pattern that lets them anticipate the next trade.
To defeat that adversary we need three independent privacy primitives, layered:
- Amount privacy. The on-chain record of the trade does not reveal the amount.
- Address privacy. The on-chain record does not link the trade to a wallet the adversary already knows.
- Timing privacy. The pattern of trades over time does not allow the adversary to reconstruct a parent strategy.
Each of those is a separate problem with separate cryptography.
Amount privacy via Pedersen commitments
The standard primitive for hiding amounts is the Pedersen commitment. A commitment to amount v is computed as C = vG + rH where G and H are generators on an elliptic curve and r is a per-commitment random blinding factor. The commitment is binding (you cannot later claim a different v) and hiding (an observer cannot recover v without knowing r).
Inside moby-privacy, every amount that goes through a privacy pool is committed before it is published. Range proofs (Bulletproofs in the v0.1 build) ensure that the committed value is positive and below an agreed maximum, which prevents minting attacks. Aggregate commitments are homomorphic, so net pool inflows and outflows can be verified to balance without ever revealing the individual amounts.
Pedersen commitments are old, well-understood cryptography. The work is in the integration: every part of the protocol that touches an amount has to be aware of whether that amount is committed or in clear text, and the type system has to make it impossible to accidentally use a committed value where a clear-text value is required. We use a Confidential<Amount> newtype throughout the crate to enforce that distinction at the type level.
Address privacy via stealth addresses
Hiding the amount is not enough if the receiving address is a known wallet. Address privacy is provided by stealth addresses: a receiver publishes a static viewing key; a sender derives a one-time payment address from that key plus per-payment randomness. The receiver can detect that a payment is theirs by scanning with the viewing key, but no other observer can link the one-time address back to the static identity.
The pattern is borrowed directly from Monero and Zcash; the implementation in moby-privacy uses curve25519-dalek for the underlying elliptic curve math. We additionally support stealth addresses on the sender side so that the on-chain record does not even reveal which wallet originated the trade — useful for desks that maintain multiple operational wallets and do not want the chain to publish the relationship between them.
Timing privacy via privacy pools
A trade can be perfectly hidden at the cryptographic layer and still leak strategy if its timing is observable. If a desk’s privacy-pool deposits and withdrawals happen on a regular schedule, the schedule itself is information.
Privacy pools defeat timing analysis with two mechanisms. First, every withdrawal carries a configurable withdrawal_delay — by default 24 hours — so the relationship between a specific deposit and a specific withdrawal is fuzzy. Second, the protocol enforces a minimum anonymity_set_size, by default 100. If fewer than 100 participants are in the pool, withdrawals queue until the set grows. The combination means a single trade is mixed with at least 99 others spread across at least 24 hours.
The cost of the timing protection is latency. A desk that needs same-block settlement cannot use the privacy pool; for that desk we offer a “stealth mode” with weaker timing guarantees but no withdrawal delay. The trade-off is explicit and chosen per trade.
Why ZK proofs instead of MPC
Renegade and several other privacy-oriented venues use multi-party computation rather than zero-knowledge proofs. MPC is a defensible choice — it produces a smaller on-chain footprint and avoids the trusted-setup question for SNARKs. We picked ZK proofs for three reasons.
First, ZK proofs do not require an online matching set. MPC dark pools need a quorum of matching nodes to be online during the trade; ZK proofs are produced by a single prover and verified by anyone. That eliminates a class of liveness problems that institutional desks find unacceptable.
Second, ZK proofs compose. The same proof system that verifies a confidential transfer can verify a compliance assertion, a jurisdiction proof, or a solvency proof. A desk that needs to prove “this trade is within my approved jurisdiction” can do it with the same machinery that verifies the trade itself. MPC requires a separate protocol for each new assertion.
Third, ZK proofs are auditable. A regulator can be given a proof and a verification key and replay the verification offline. MPC outputs are harder to convince a regulator about because the proof is implicit in the protocol execution rather than carried as an explicit object.
The moby-privacy crate supports four proof systems behind a common trait: Groth16 for the smallest verification cost, PLONK for trusted-setup-free updates, STARKs for post-quantum security, and Bulletproofs for the range-proof workload. A desk can choose the system per pool depending on which trade-off matters most.
Selective disclosure: the compliance story
The hardest part of institutional privacy is not the cryptography. It is convincing a regulator that the privacy guarantees do not become an exemption from regulation. The answer is selective disclosure: a trader can produce a proof of any specific assertion about a hidden trade without revealing the trade itself.
In practice this looks like a small library of disclosure circuits inside moby-privacy. The trader can prove:
- The trade amount was below a regulatory threshold without revealing the amount.
- The counterparty was on an approved list without revealing which counterparty.
- The trade was executed in an approved jurisdiction without revealing the geographic identifier.
- The trader holds accreditation issued by an approved provider without revealing identity.
Each disclosure is a ZK proof against the same commitment that hides the underlying value. A regulator gets a verifiable assertion; a competitor watching the chain gets a commitment that reveals nothing.
The disclosure machinery is the difference between a privacy protocol that exists only on adversarial blogs and one that an institutional compliance team will actually approve. We spend disproportionate engineering time on the disclosure circuits because they are the part that determines whether the rest of the crate gets used in production.
What we did not solve
A few things are out of scope and worth naming.
Off-chain leakage. If a trader posts a screenshot of their position to a Discord, no protocol primitive can help. Privacy ends at the boundary of the on-chain system.
Custody-side leakage. A custodian can observe a desk’s flow into and out of the privacy pool. If the custodian is adversarial, the privacy guarantees do not extend through them. For desks where this matters, multi-custodian fragmentation is the workaround.
Long-run statistical analysis. Sufficiently rich data and sufficient time will degrade any anonymity set. The 100-participant minimum is a current default, not a final answer. Pools used by desks with very specific flow shapes will probably want larger anonymity sets and longer withdrawal delays.
Post-quantum guarantees beyond STARK pools. Groth16, PLONK, and Bulletproofs are not post-quantum secure. Pools that need long-term confidentiality should be configured with STARK proofs and post-quantum signature schemes. The cost is larger proofs and slower verification; for desks with multi-year confidentiality horizons it is worth paying.
What this enables
The three primitives — committed amounts, stealth addresses, anonymity-set pools — combine into a usable on-chain privacy story. A desk can route a $50M ETH-to-SOL trade through the privacy pool, receive the SOL at a fresh stealth address, prove to its compliance team that the trade was within policy via a selective-disclosure proof, and leave nothing on-chain that a competitor can attribute back to the originating desk.
That is the bar institutional privacy has to clear. Anything weaker collapses under the threat model and gets dismissed by the desks that actually need it. Anything stronger imposes costs (MPC liveness, custodian fragmentation, post-quantum proofs) that the desks have to opt into trade by trade. The job of moby-privacy is to make each trade-off explicit, fast, and verifiable.
The protocol is MIT and self-hostable. If you want the team to scope an engagement around your size, or write to hello@mobymarket.cryptuon.com.